timeit --- Đo thời gian thực thi các đoạn mã nhỏ

Source code: Lib/timeit.py


Mô-đun này cung cấp một cách đơn giản để tính thời gian cho các đoạn mã Python nhỏ. Nó có cả Giao diện dòng lệnh cũng như callable. Nó tránh được một số bẫy phổ biến để đo thời gian thực hiện. Xem thêm phần giới thiệu của Tim Peters về chương "Thuật toán" trong ấn bản thứ hai của Python Cookbook, do O'Reilly xuất bản.

Ví dụ cơ bản

Ví dụ sau đây cho thấy cách sử dụng Giao diện dòng lệnh để so sánh ba biểu thức khác nhau:

$ python -m timeit "'-'.join(str(n) for n in range(100))"
10000 vòng lặp, tốt nhất là 5: 30,2 usec mỗi vòng lặp
$ python -m timeit "'-'.join([str(n) for n in range(100)])"
10000 vòng lặp, tốt nhất là 5: 27,5 usec mỗi vòng lặp
$ python -m timeit "'-'.join(map(str, range(100)))"
10000 vòng, tốt nhất là 5: 23,2 usec mỗi vòng

Điều này có thể đạt được từ Giao diện Python với:

>>> nhập thời gian
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

Một lệnh gọi cũng có thể được truyền từ Giao diện Python:

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

Tuy nhiên, xin lưu ý rằng timeit() sẽ tự động xác định số lần lặp lại khi sử dụng giao diện dòng lệnh. Trong phần Ví dụ bạn có thể tìm thấy các ví dụ nâng cao hơn.

Giao diện Python

Mô-đun này xác định ba hàm tiện lợi và một lớp công khai:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Tạo một phiên bản Timer với câu lệnh đã cho, mã setup và hàm timer rồi chạy phương thức timeit() của nó với các lần thực thi number. Đối số globals tùy chọn chỉ định một không gian tên để thực thi mã.

Thay đổi trong phiên bản 3.5: Tham số globals tùy chọn đã được thêm vào.

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

Tạo một phiên bản Timer với câu lệnh đã cho, mã setup và hàm timer rồi chạy phương thức repeat() của nó với số lần thực thi repeatnumber đã cho. Đối số globals tùy chọn chỉ định một không gian tên để thực thi mã.

Thay đổi trong phiên bản 3.5: Tham số globals tùy chọn đã được thêm vào.

Thay đổi trong phiên bản 3.7: Giá trị mặc định của repeat đã thay đổi từ 3 thành 5.

timeit.default_timer()

Bộ hẹn giờ mặc định, luôn là time.perf_counter(), trả về số float. Một cách khác, time.perf_counter_ns, trả về số nguyên nano giây.

Thay đổi trong phiên bản 3.3: time.perf_counter() hiện là bộ đếm thời gian mặc định.

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

Lớp dành cho tốc độ thực thi thời gian của các đoạn mã nhỏ.

Hàm tạo lấy một câu lệnh để tính thời gian, một câu lệnh bổ sung được sử dụng để thiết lập và một hàm hẹn giờ. Cả hai câu lệnh đều mặc định là 'pass'; chức năng hẹn giờ phụ thuộc vào nền tảng (xem chuỗi tài liệu mô-đun). stmtsetup cũng có thể chứa nhiều câu lệnh được phân tách bằng ; hoặc dòng mới, miễn là chúng không chứa các chuỗi ký tự nhiều dòng. Theo mặc định, câu lệnh sẽ được thực thi trong không gian tên của timeit; hành vi này có thể được kiểm soát bằng cách chuyển một không gian tên tới globals.

Để đo thời gian thực hiện của câu lệnh đầu tiên, hãy sử dụng phương thức timeit(). Các phương thức repeat()autorange() là các phương thức tiện lợi để gọi timeit() nhiều lần.

Thời gian thực thi của setup được loại trừ khỏi quá trình thực thi được tính thời gian tổng thể.

Các tham số stmtsetup cũng có thể lấy các đối tượng có thể gọi được mà không cần đối số. Điều này sẽ nhúng các cuộc gọi đến chúng trong một chức năng hẹn giờ, sau đó sẽ được timeit() thực thi. Lưu ý rằng chi phí thời gian lớn hơn một chút trong trường hợp này do có các lệnh gọi hàm bổ sung.

Thay đổi trong phiên bản 3.5: Tham số globals tùy chọn đã được thêm vào.

timeit(number=1000000)

Thời gian thực thi number của câu lệnh chính. Điều này thực thi câu lệnh thiết lập một lần và sau đó trả về thời gian cần thiết để thực thi câu lệnh chính một số lần. Bộ hẹn giờ mặc định trả về giây dưới dạng float. Đối số là số lần thực hiện vòng lặp, mặc định là một triệu. Câu lệnh chính, câu lệnh thiết lập và hàm hẹn giờ sẽ được sử dụng sẽ được chuyển đến hàm tạo.

Ghi chú

Theo mặc định, timeit() tạm thời tắt garbage collection trong thời gian tính giờ. Ưu điểm của phương pháp này là nó làm cho việc định thời gian độc lập trở nên dễ so sánh hơn. Điểm bất lợi là GC có thể là một thành phần quan trọng trong việc thực hiện chức năng được đo. Nếu vậy, GC có thể được kích hoạt lại dưới dạng câu lệnh đầu tiên trong chuỗi setup. Ví dụ:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

Tự động xác định số lần gọi timeit().

Đây là một hàm tiện lợi gọi timeit() liên tục sao cho tổng thời gian >= 0,2 giây, trả về kết quả cuối cùng (số vòng lặp, thời gian thực hiện cho số vòng lặp đó). Nó gọi timeit() với số lượng tăng dần từ dãy 1, 2, 5, 10, 20, 50,... cho đến khi hết thời gian ít nhất là 0,2 giây.

Nếu callback được đưa ra và không phải là None, nó sẽ được gọi sau mỗi lần thử với hai đối số: callback(number, time_taken).

Added in version 3.6.

repeat(repeat=5, number=1000000)

Gọi timeit() vài lần.

Đây là một chức năng tiện lợi gọi timeit() liên tục, trả về danh sách kết quả. Đối số đầu tiên chỉ định số lần gọi timeit(). Đối số thứ hai chỉ định đối số number cho timeit().

Ghi chú

Thật thú vị khi tính giá trị trung bình và độ lệch chuẩn từ vectơ kết quả và báo cáo những điều này. Tuy nhiên, điều này không hữu ích lắm. Trong trường hợp điển hình, giá trị thấp nhất đưa ra giới hạn dưới về tốc độ máy của bạn có thể chạy đoạn mã đã cho; các giá trị cao hơn trong vectơ kết quả thường không phải do sự thay đổi về tốc độ của Python mà do các quá trình khác cản trở độ chính xác về thời gian của bạn. Vì vậy, min() của kết quả có lẽ là con số duy nhất bạn nên quan tâm. Sau đó, bạn nên xem xét toàn bộ vectơ và áp dụng cảm giác thông thường hơn là thống kê.

Thay đổi trong phiên bản 3.7: Giá trị mặc định của repeat đã thay đổi từ 3 thành 5.

print_exc(file=None)

Trình trợ giúp in dấu vết từ mã thời gian.

Sử dụng điển hình:

t = Hẹn giờ(...) # outside thử/ngoại trừ
thử:
    t.timeit(...) # or t.repeat(...)
ngoại trừ Ngoại lệ:
    t.print_exc()

Ưu điểm so với truy nguyên tiêu chuẩn là các dòng nguồn trong mẫu đã biên dịch sẽ được hiển thị. Đối số file tùy chọn chỉ dẫn nơi gửi truy nguyên; nó mặc định là sys.stderr.

Giao diện dòng lệnh

Khi được gọi là một chương trình từ dòng lệnh, biểu mẫu sau được sử dụng

python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-h] [câu lệnh ...]

Trường hợp các tùy chọn sau được hiểu:

-n N, --number=N

bao nhiêu lần để thực hiện 'câu lệnh'

-r N, --repeat=N

bao nhiêu lần để lặp lại bộ đếm thời gian (mặc định 5)

-s S, --setup=S

câu lệnh được thực thi một lần ban đầu (pass mặc định)

-p, --process

đo thời gian xử lý, không phải thời gian treo tường, sử dụng time.process_time() thay vì time.perf_counter(), đây là mặc định

Added in version 3.3.

-u, --unit=U

chỉ định đơn vị thời gian cho đầu ra bộ đếm thời gian; có thể chọn nsec, usec, msec hoặc sec

Added in version 3.5.

-v, --verbose

in kết quả thời gian thô; lặp lại để có độ chính xác cao hơn

-h, --help

in một thông báo sử dụng ngắn và thoát

Một câu lệnh nhiều dòng có thể được đưa ra bằng cách chỉ định mỗi dòng làm một đối số câu lệnh riêng biệt; Có thể thụt dòng bằng cách đặt đối số trong dấu ngoặc kép và sử dụng dấu cách ở đầu. Nhiều tùy chọn -s được xử lý tương tự.

Nếu không đưa ra -n, số vòng lặp phù hợp sẽ được tính bằng cách thử tăng số lượng từ dãy 1, 2, 5, 10, 20, 50, ... cho đến khi tổng thời gian ít nhất là 0,2 giây.

Các phép đo default_timer() có thể bị ảnh hưởng bởi các chương trình khác đang chạy trên cùng một máy, vì vậy điều tốt nhất nên làm khi cần tính thời gian chính xác là lặp lại thời gian một vài lần và sử dụng thời gian tốt nhất. Tùy chọn -r phù hợp cho việc này; mặc định 5 lần lặp lại có lẽ là đủ trong hầu hết các trường hợp. Bạn có thể sử dụng time.process_time() để đo thời gian CPU.

Ghi chú

Có một chi phí cơ bản nhất định liên quan đến việc thực thi một câu lệnh vượt qua. Mã ở đây không cố gắng che giấu nó, nhưng bạn nên biết về nó. Chi phí cơ bản có thể được đo bằng cách gọi chương trình mà không có đối số và nó có thể khác nhau giữa các phiên bản Python.

Ví dụ

Có thể cung cấp một câu lệnh thiết lập chỉ được thực hiện một lần khi bắt đầu:

$ python -m timeit -s "text = 'chuỗi mẫu'; char = 'g'" "char trong văn bản"
5000000 vòng lặp, tốt nhất là 5: 0,0877 usec mỗi vòng lặp
$ python -m timeit -s "text = 'chuỗi mẫu'; char = 'g'" "text.find(char)"
1000000 vòng lặp, tốt nhất là 5: 0,342 usec mỗi vòng lặp

Trong đầu ra, có ba trường. Số vòng lặp cho bạn biết số lần nội dung câu lệnh được chạy trong mỗi lần lặp lại vòng lặp định thời gian. Số lần lặp lại ("tốt nhất trong 5") cho bạn biết vòng lặp thời gian được lặp lại bao nhiêu lần và cuối cùng là thời gian trung bình của nội dung câu lệnh trong lần lặp lại tốt nhất của vòng lặp thời gian. Tức là lấy thời gian lặp lại nhanh nhất chia cho số vòng lặp.

>>> nhập thời gian
>>> timeit.timeit('char trong văn bản', setup='text = "chuỗi mẫu"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "chuỗi mẫu"; char = "g"')
1.7246671520006203

Điều tương tự có thể được thực hiện bằng cách sử dụng lớp Timer và các phương thức của nó

>>> nhập thời gian
>>> t = timeit.Timer('char trong văn bản', setup='text = "chuỗi mẫu"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0,40183617287970225, 0,37027556854118704, 0,38344867356679524, 0,3712595970846668, 0,37866875250654886]

Các ví dụ sau đây cho thấy cách tính thời gian cho các biểu thức có nhiều dòng. Ở đây chúng tôi so sánh chi phí sử dụng hasattr() với try/except để kiểm tra các thuộc tính đối tượng bị thiếu và hiện tại:

$ python -m timeit "thử:" " str.__bool__" "ngoại trừ AttributionError:" " pass"
20000 vòng lặp, tốt nhất là 5: 15,7 usec mỗi vòng lặp
$ python -m timeit "if hasattr(str, '__bool__'): vượt qua"
50000 vòng lặp, tốt nhất là 5: 4,26 usec mỗi vòng lặp

$ python -m timeit "try:" " int.__bool__" "ngoại trừ AttributionError:" " pass"
200000 vòng lặp, tốt nhất là 5: 1,43 usec mỗi vòng lặp
$ python -m timeit "if hasattr(int, '__bool__'): vượt qua"
100000 vòng lặp, tốt nhất là 5: 2,23 usec mỗi vòng lặp
>>> nhập thời gian
>>> # attribute bị thiếu
>>> s = """\
... thử:
... str.__bool__
... ngoại trừ AttributionError:
... vượt qua
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): vượt qua"
>>> timeit.timeit(stmt=s, number=100000)
0,5829014980008651
>>>
>>> # attribute có mặt
>>> s = """\
... thử:
... int.__bool__
... ngoại trừ AttributionError:
... vượt qua
... """
>>> timeit.timeit(stmt=s, number=100000)
0,04215312199994514
>>> s = "if hasattr(int, '__bool__'): vượt qua"
>>> timeit.timeit(stmt=s, number=100000)
0,08588060699912603

Để cấp cho mô-đun timeit quyền truy cập vào các hàm bạn xác định, bạn có thể chuyển tham số setup chứa câu lệnh nhập

kiểm tra chắc chắn():
    """Chức năng kiểm tra ngu ngốc"""
    L = [i cho i trong phạm vi (100)]

nếu __name__ == '__main__':
    thời gian nhập khẩu
    print(timeit.timeit("test()", setup="from __main__ import test"))

Một tùy chọn khác là chuyển globals() cho tham số globals, điều này sẽ khiến mã được thực thi trong không gian tên chung hiện tại của bạn. Điều này có thể thuận tiện hơn việc chỉ định riêng từng lần nhập:

định nghĩa f(x):
    trả lại x**2
định nghĩa g(x):
    trả lại x**4
định nghĩa h(x):
    trả lại x**8

thời gian nhập khẩu
print(timeit.timeit('[func(42) for func in (f,g,h)]', Globals=globals()))