Cách sử dụng mô-đun timeit


351

Tôi hiểu khái niệm về những gì timeitlàm nhưng tôi không chắc làm thế nào để thực hiện nó trong mã của tôi.

Làm thế nào tôi có thể so sánh hai chức năng, nói insertion_sorttim_sort, với timeit?

Câu trả lời:


266

Cách thức hoạt động của timeit là chạy mã thiết lập một lần và sau đó thực hiện các cuộc gọi lặp lại cho một loạt các câu lệnh. Vì vậy, nếu bạn muốn kiểm tra sắp xếp, cần có sự cẩn thận để một lần vượt qua tại một vị trí tại chỗ không ảnh hưởng đến lần tiếp theo với dữ liệu đã được sắp xếp (tất nhiên, điều đó sẽ khiến Timsort thực sự tỏa sáng vì nó hoạt động tốt nhất khi dữ liệu đã được đặt hàng một phần).

Dưới đây là một ví dụ về cách thiết lập thử nghiệm để sắp xếp:

>>> import timeit

>>> setup = '''
import random

random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''

>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145

Lưu ý rằng một loạt các báo cáo tạo một bản sao mới của dữ liệu chưa được sắp xếp trên mỗi lần vượt qua.

Ngoài ra, lưu ý kỹ thuật thời gian chạy bộ đo lường bảy lần và chỉ giữ thời gian tốt nhất - điều này thực sự có thể giúp giảm các biến dạng đo do các quy trình khác chạy trên hệ thống của bạn.

Đó là những lời khuyên của tôi để sử dụng thời gian chính xác. Hi vọng điêu nay co ich :-)


8
Có, nó bao gồm bản sao danh sách (rất nhanh so với chính nó). Nếu bạn không sao chép, thì lượt đầu tiên sắp xếp danh sách và số còn lại sẽ không phải thực hiện bất kỳ công việc nào. Nếu bạn muốn biết thời gian chỉ dành cho việc sắp xếp, thì hãy chạy phần trên có và không có timsort(a)và lấy sự khác biệt :-)
Raymond Hettinger

Tôi khuyên bạn nên lặp lại 7 lần cho mỗi thiết lập, và sau đó trung bình; chứ không phải là cách khác. Bằng cách này, nếu mỗi đột biến do các quá trình khác có cơ hội tốt bị bỏ qua hoàn toàn, thay vì tính trung bình.
tối đa

75
@max Sử dụng min () thay vì trung bình của thời gian. Đó là một lời khuyên từ tôi, từ Tim Peters và từ Guido van Rossum. Thời gian nhanh nhất thể hiện tốt nhất một thuật toán có thể thực hiện khi bộ đệm được tải và hệ thống không bận rộn với các tác vụ khác. Tất cả các thời gian là ồn ào - thời gian nhanh nhất là ít ồn ào nhất. Thật dễ dàng để chỉ ra rằng thời gian nhanh nhất là có thể lặp lại nhiều nhất và do đó hữu ích nhất khi tính thời gian hai lần thực hiện khác nhau.
Raymond Hettinger

4
Bạn tính trung bình (tốt, tổng cộng, nhưng tương đương) cho 1000 đầu vào; sau đó lặp lại 7 lần, và lấy tối thiểu . Bạn cần trung bình hơn 1000 đầu vào vì bạn muốn độ phức tạp thuật toán trung bình (không phải trường hợp tốt nhất). Bạn cần tối thiểu cho chính xác lý do bạn đưa ra. Tôi nghĩ rằng tôi có thể cải thiện cách tiếp cận của bạn bằng cách chọn một đầu vào, chạy thuật toán 7 lần, lấy mức tối thiểu; sau đó lặp lại cho 1000 đầu vào khác nhau và lấy mức trung bình. Những gì tôi đã không nhận ra là bạn .repeat(7,1000)đã làm điều này (bằng cách sử dụng cùng một hạt giống)! Vì vậy, giải pháp của bạn là IMO hoàn hảo.
tối đa

5
Tôi chỉ có thể thêm rằng cách bạn phân bổ ngân sách 7000 lần thực hiện (ví dụ: .repeat(7, 1000)so với so .repeat(2, 3500)với .repeat(35, 200) sẽ phụ thuộc vào mức độ lỗi do tải hệ thống so với lỗi do biến thiên đầu vào. Trong trường hợp cực đoan nếu hệ thống của bạn luôn chịu tải nặng và bạn thấy một cái đuôi dài mỏng ở bên trái phân phối thời gian thực hiện (khi bạn bắt nó ở trạng thái không hoạt động hiếm), bạn thậm chí có thể thấy .repeat(7000,1)hữu ích hơn .repeat(7,1000)nếu bạn ngân sách không thể hơn 7000 lượt chạy.
tối đa

277

Nếu bạn muốn sử dụng timeittrong phiên Python tương tác, có hai tùy chọn thuận tiện:

  1. Sử dụng vỏ IPython . Nó có tính năng %timeitđặc biệt thuận tiện :

    In [1]: def f(x):
       ...:     return x*x
       ...: 
    
    In [2]: %timeit for x in range(100): f(x)
    100000 loops, best of 3: 20.3 us per loop
  2. Trong trình thông dịch Python chuẩn, bạn có thể truy cập các hàm và các tên khác mà bạn đã xác định trước đó trong phiên tương tác bằng cách nhập chúng từ __main__trong câu lệnh thiết lập:

    >>> def f(x):
    ...     return x * x 
    ... 
    >>> import timeit
    >>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
                      number=100000)
    [2.0640320777893066, 2.0876040458679199, 2.0520210266113281]

97
+1 để hiển thị from __main__ import fkỹ thuật. Tôi không nghĩ rằng điều này được biết đến rộng rãi như nó phải được. Nó rất hữu ích trong các trường hợp như thế này trong đó một lệnh gọi hàm hoặc phương thức đang được định thời gian. Trong các trường hợp khác (định thời gian cho một loạt các bước), nó ít hữu ích hơn vì nó giới thiệu chức năng gọi vốn.
Raymond Hettinger

15
Bạn chỉ có thể làm%timeit f(x)
qed

Lưu ý: thiết lập "nhập f" làm cho quyền truy cập vào fa đọc cục bộ nhanh - không phản ánh chính xác lệnh gọi hàm toàn cầu (của hàm nhanh ngắn) trong mã thông thường điển hình. Trong Py3.5 + toàn cầu thực có thể được cung cấp: "Thay đổi trong phiên bản 3.5: Tham số toàn cầu tùy chọn đã được thêm vào."; Trước toàn cầu của mô-đun thời gian, nơi không thể tránh khỏi (điều này không có ý nghĩa nhiều). Có thể toàn cầu của mã gọi ( sys._getframe(N).f_globals) phải là mặc định ngay từ đầu.
kxr

140

Tôi sẽ cho bạn biết một bí mật: cách tốt nhất để sử dụng timeitlà trên dòng lệnh.

Trên dòng lệnh, timeitphân tích thống kê phù hợp: nó cho bạn biết thời gian chạy ngắn nhất. Điều này là tốt bởi vì tất cả các lỗi trong thời gian là tích cực. Vì vậy, thời gian ngắn nhất có ít lỗi nhất trong đó. Không có cách nào để có được lỗi tiêu cực vì một máy tính không thể tính toán nhanh hơn nó có thể tính toán!

Vì vậy, giao diện dòng lệnh:

%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop

Điều đó khá đơn giản, phải không?

Bạn có thể thiết lập công cụ:

%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop

cái nào cũng hữu ích

Nếu bạn muốn nhiều dòng, bạn có thể sử dụng tiếp tục tự động của shell hoặc sử dụng các đối số riêng biệt:

%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop

Điều đó cho phép thiết lập

x = range(1000)
y = range(100)

và thời gian

sum(x)
min(y)

Nếu bạn muốn có các tập lệnh dài hơn, bạn có thể muốn chuyển sang timeitbên trong tập lệnh Python. Tôi đề nghị tránh điều đó bởi vì phân tích và thời gian chỉ đơn giản là tốt hơn trên dòng lệnh. Thay vào đó, tôi có xu hướng tạo các kịch bản shell:

 SETUP="

 ... # lots of stuff

 "

 echo Minmod arr1
 python -m timeit -s "$SETUP" "Minmod(arr1)"

 echo pure_minmod arr1
 python -m timeit -s "$SETUP" "pure_minmod(arr1)"

 echo better_minmod arr1
 python -m timeit -s "$SETUP" "better_minmod(arr1)"

 ... etc

Việc này có thể mất nhiều thời gian hơn do nhiều lần khởi tạo, nhưng thông thường đó không phải là vấn đề lớn.


Nhưng nếu bạn muốn sử dụng timeitbên trong mô-đun của bạn thì sao?

Cách đơn giản là làm:

def function(...):
    ...

timeit.Timer(function).timeit(number=NUMBER)

và điều đó cho bạn thời gian tích lũy ( không phải tối thiểu!) để chạy số lần đó.

Để có được một phân tích tốt, sử dụng .repeatvà lấy tối thiểu:

min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

Bạn thường nên kết hợp điều này với functools.partialthay vì lambda: ...để chi phí thấp hơn. Vì vậy, bạn có thể có một cái gì đó như:

from functools import partial

def to_time(items):
    ...

test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)

# Divide by the number of repeats
time_taken = min(times) / 1000

Bạn cũng có thể làm:

timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

sẽ cung cấp cho bạn một cái gì đó gần hơn với giao diện từ dòng lệnh, nhưng theo cách ít thú vị hơn nhiều. Việc "from __main__ import ..."cho phép bạn sử dụng mã từ mô-đun chính của mình bên trong môi trường nhân tạo được tạo bởi timeit.

Điều đáng chú ý là đây là một trình bao bọc tiện lợi Timer(...).timeit(...)và vì vậy không đặc biệt tốt về thời gian. Cá nhân tôi thích sử dụng Timer(...).repeat(...)như tôi đã trình bày ở trên.


Cảnh báo

Có một vài hãy cẩn thận timeitmà giữ ở khắp mọi nơi.

  • Chi phí không được tính. Nói rằng bạn muốn thời gian x += 1, để tìm hiểu thêm bao lâu:

    >>> python -m timeit -s "x = 0" "x += 1"
    10000000 loops, best of 3: 0.0476 usec per loop

    Chà, không phải là 0,0476. Bạn chỉ biết rằng nó ít hơn thế. Tất cả lỗi là tích cực.

    Vì vậy, hãy thử và tìm chi phí thuần túy :

    >>> python -m timeit -s "x = 0" ""      
    100000000 loops, best of 3: 0.014 usec per loop

    Đó là một chi phí tốt 30% chỉ từ thời gian! Điều này có thể ồ ạt thời gian tương đối. Nhưng bạn chỉ thực sự quan tâm đến việc thêm thời gian; thời gian tra cứu xcũng cần được đưa vào chi phí:

    >>> python -m timeit -s "x = 0" "x"
    100000000 loops, best of 3: 0.0166 usec per loop

    Sự khác biệt không lớn hơn nhiều, nhưng nó ở đó.

  • Phương pháp đột biến là nguy hiểm.

    >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
    10000000 loops, best of 3: 0.0436 usec per loop

    Nhưng điều đó hoàn toàn sai! xlà danh sách trống sau lần lặp đầu tiên. Bạn sẽ cần phải xác định lại:

    >>> python -m timeit "x = [0]*100000" "while x: x.pop()"
    100 loops, best of 3: 9.79 msec per loop

    Nhưng sau đó bạn có rất nhiều chi phí. Tài khoản cho riêng biệt.

    >>> python -m timeit "x = [0]*100000"                   
    1000 loops, best of 3: 261 usec per loop

    Lưu ý rằng trừ chi phí là hợp lý ở đây chỉ vì chi phí là một phần nhỏ của thời gian.

    Ví dụ của bạn, đáng chú ý là cả Sắp xếp chèn và Sắp xếp thời gian có hành vi thời gian hoàn toàn bất thường cho các danh sách đã được sắp xếp. Điều này có nghĩa là bạn sẽ yêu cầu random.shufflegiữa các loại nếu bạn muốn tránh làm hỏng thời gian của mình.


1
usec có nghĩa là gì? nó là micro giây?
Hasan Iqbal

2
@HasanIqbalAnik Có.
Veedrac

@StefanPochmann Vì nó không thử lấy mẫu nhiều lần.
Veedrac

Độc giả của câu trả lời này cũng có thể quan tâm đến việc sử dụng Python timeittừ một chương trình nhưng hoạt động giống như dòng lệnh? .
Graham

@Veedrac Xem xét tuyên bố của bạn về việc trừ chi phí thời gian thuần túy, timeitthực thi một passtuyên bố khi không có đối số nào được đưa ra, tất nhiên, sẽ mất một thời gian. Nếu bất kỳ đối số nào được đưa ra, passsẽ không được thực thi, do đó, trừ đi một số 0.014usec từ mọi thời điểm sẽ không chính xác.
Arne

99

Nếu bạn muốn so sánh nhanh hai khối mã / hàm, bạn có thể làm:

import timeit

start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)

start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)

43

Tôi thấy cách dễ nhất để sử dụng timeit là từ dòng lệnh:

Đã cho test.txt :

def InsertionSort(): ...
def TimSort(): ...

chạy thời gian như thế này:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'

18

Đối với tôi, đây là cách nhanh nhất:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)

12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)

7

Điều này làm việc tuyệt vời:

  python -m timeit -c "$(cat file_name.py)"

Điều gì sẽ tương đương với Windows?
Shailen

2
Làm thế nào để bạn vượt qua các tham số, nếu kịch bản yêu cầu bất kỳ?
Juuso Ohtonen

3

cho phép thiết lập cùng một từ điển trong mỗi điều sau đây và kiểm tra thời gian thực hiện.

Đối số thiết lập về cơ bản là thiết lập từ điển

Số là để chạy mã 1000000 lần. Không phải thiết lập mà là stmt

Khi bạn chạy nó, bạn có thể thấy chỉ số đó nhanh hơn get. Bạn có thể chạy nó nhiều lần để xem.

Mã về cơ bản cố gắng để có được giá trị của c trong từ điển.

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

Đây là kết quả của tôi, của bạn sẽ khác nhau.

theo chỉ số: 0.20900007452246427

bằng cách nhận: 0,54841166886888


Phiên bản nào của python bạn đang sử dụng?
Eduardo

3

chỉ cần chuyển toàn bộ mã của bạn dưới dạng đối số của timeit:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))


0

Mô-đun timeit tích hợp hoạt động tốt nhất từ ​​dòng lệnh IPython.

Để chức năng thời gian từ trong một mô-đun:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result

0

Ví dụ về cách sử dụng trình thông dịch REPL của Python với chức năng chấp nhận các tham số.

>>> import timeit                                                                                         

>>> def naive_func(x):                                                                                    
...     a = 0                                                                                             
...     for i in range(a):                                                                                
...         a += i                                                                                        
...     return a                                                                                          

>>> def wrapper(func, *args, **kwargs):                                                                   
...     def wrapper():                                                                                    
...         return func(*args, **kwargs)                                                                  
...     return wrapper                                                                                    

>>> wrapped = wrapper(naive_func, 1_000)                                                                  

>>> timeit.timeit(wrapped, number=1_000_000)                                                              
0.4458435332577161                                                                                        

0

Bạn sẽ tạo hai chức năng và sau đó chạy một cái gì đó tương tự như thế này. Lưu ý, bạn muốn chọn cùng số lần thực hiện / chạy để so sánh apple với apple.
Điều này đã được thử nghiệm theo Python 3.7.

nhập mô tả hình ảnh ở đây Đây là mã để dễ dàng sao chép nó

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.