timeit --- 測量小代碼片段的執行時(shí)間?

源碼: Lib/timeit.py


此模塊提供了一種簡(jiǎn)單的方法來(lái)計算一小段 Python 代碼的耗時(shí)。 它有 命令行接口 以及一個(gè) 可調用 方法。 它避免了許多測量時(shí)間的常見(jiàn)陷阱。 另見(jiàn) Tim Peter 在 O'Reilly 出版的 Python Cookbook 第二版中“算法”章節的概述。

基本示例?

以下示例顯示了如何使用 命令行接口 來(lái)比較三個(gè)不同的表達式:

$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 5: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 5: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 5: 23.2 usec per loop

這可以通過(guò) Python 接口 實(shí)現

>>>
>>> import timeit
>>> 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

Python 接口 還可以傳出一個(gè)可調用對象:

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

但請注意 timeit() 僅在使用命令行界面時(shí)會(huì )自動(dòng)確定重復次數。 在 例子 一節你可以找到更多的進(jìn)階示例。

Python 接口?

該模塊定義了三個(gè)便利函數和一個(gè)公共類(lèi):

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

使用給定語(yǔ)句、 setup 代碼和 timer 函數創(chuàng )建一個(gè) Timer 實(shí)例,并執行 number 次其 timeit() 方法??蛇x的 globals 參數指定用于執行代碼的命名空間。

在 3.5 版更改: 添加可選參數 globals 。

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

使用給定語(yǔ)句、 setup 代碼和 timer 函數創(chuàng )建一個(gè) Timer 實(shí)例,并使用給定的 repeat 計數和 number 執行運行其 repeat() 方法??蛇x的 globals 參數指定用于執行代碼的命名空間。

在 3.5 版更改: 添加可選參數 globals 。

在 3.7 版更改: repeat 的默認值由 3 更改為 5 。

timeit.default_timer()?

默認的計時(shí)器,總是 time.perf_counter() 。

在 3.3 版更改: time.perf_counter() 現在是默認計時(shí)器。

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

用于小代碼片段的計數執行速度的類(lèi)。

構造函數接受一個(gè)將計時(shí)的語(yǔ)句、一個(gè)用于設置的附加語(yǔ)句和一個(gè)定時(shí)器函數。兩個(gè)語(yǔ)句都默認為 'pass' ;計時(shí)器函數與平臺有關(guān)(請參閱模塊文檔字符串)。 stmtsetup 也可能包含多個(gè)以 ; 或換行符分隔的語(yǔ)句,只要它們不包含多行字符串文字即可。該語(yǔ)句默認在 timeit 的命名空間內執行;可以通過(guò)將命名空間傳遞給 globals 來(lái)控制此行為。

要測量第一個(gè)語(yǔ)句的執行時(shí)間,請使用 timeit() 方法。 repeat()autorange() 方法是方便的方法來(lái)調用 timeit() 多次。

setup 的執行時(shí)間從總體計時(shí)執行中排除。

stmtsetup 參數也可以使用不帶參數的可調用對象。這將在一個(gè)計時(shí)器函數中嵌入對它們的調用,然后由 timeit() 執行。請注意,由于額外的函數調用,在這種情況下,計時(shí)開(kāi)銷(xiāo)會(huì )略大一些。

在 3.5 版更改: 添加可選參數 globals 。

timeit(number=1000000)?

執行 number 次主要語(yǔ)句。這將執行一次 setup 語(yǔ)句,然后返回執行主語(yǔ)句多次所需的時(shí)間,以秒為單位測量為浮點(diǎn)數。參數是通過(guò)循環(huán)的次數,默認為一百萬(wàn)。要使用的主語(yǔ)句、 setup 語(yǔ)句和 timer 函數將傳遞給構造函數。

備注

默認情況下, timeit() 暫時(shí)關(guān)閉 garbage collection 。這種方法的優(yōu)點(diǎn)在于它使獨立時(shí)序更具可比性。缺點(diǎn)是GC可能是所測量功能性能的重要組成部分。如果是這樣,可以在 setup 字符串中的第一個(gè)語(yǔ)句重新啟用GC。例如:

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

自動(dòng)決定調用多少次 timeit() 。

這是一個(gè)便利函數,它反復調用 timeit() ,以便總時(shí)間 >= 0.2 秒,返回最終(循環(huán)次數,循環(huán)所用的時(shí)間)。它調用 timeit() 的次數以序列 1, 2, 5, 10, 20, 50, ... 遞增,直到所用的時(shí)間至少為0.2秒。

如果給出 callback 并且不是 None ,則在每次試驗后將使用兩個(gè)參數調用它: callback(number, time_taken) 。

3.6 新版功能.

repeat(repeat=5, number=1000000)?

調用 timeit() 幾次。

這是一個(gè)方便的函數,它反復調用 timeit() ,返回結果列表。第一個(gè)參數指定調用 timeit() 的次數。第二個(gè)參數指定 timeit()number 參數。

備注

從結果向量計算并報告平均值和標準差這些是很誘人的。但是,這不是很有用。在典型情況下,最低值給出了機器運行給定代碼段的速度的下限;結果向量中較高的值通常不是由Python的速度變化引起的,而是由于其他過(guò)程干擾你的計時(shí)準確性。所以結果的 min() 可能是你應該感興趣的唯一數字。之后,你應該看看整個(gè)向量并應用常識而不是統計。

在 3.7 版更改: repeat 的默認值由 3 更改為 5 。

print_exc(file=None)?

幫助程序從計時(shí)代碼中打印回溯。

典型使用:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

與標準回溯相比,優(yōu)勢在于將顯示已編譯模板中的源行??蛇x的 file 參數指向發(fā)送回溯的位置;它默認為 sys.stderr 。

命令行接口?

從命令行調用程序時(shí),使用以下表單:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]

如果了解以下選項:

-n N, --number=N?

執行 '語(yǔ)句' 多少次

-r N, --repeat=N?

重復計時(shí)器的次數(默認為5)

-s S, --setup=S?

最初要執行一次的語(yǔ)句(默認為 pass

-p, --process?

測量進(jìn)程時(shí)間,而不是 wallclock 時(shí)間,使用 time.process_time() 而不是 time.perf_counter() ,這是默認值

3.3 新版功能.

-u, --unit=U?

specify a time unit for timer output; can select nsec, usec, msec, or sec

3.5 新版功能.

-v, --verbose?

打印原始計時(shí)結果;重復更多位數精度

-h, --help?

打印一條簡(jiǎn)短的使用信息并退出

可以通過(guò)將每一行指定為單獨的語(yǔ)句參數來(lái)給出多行語(yǔ)句;通過(guò)在引號中包含參數并使用前導空格可以縮進(jìn)行。多個(gè) -s 選項的處理方式相似。

如果未給出 -n,則會(huì )通過(guò)嘗試按序列 1, 2, 5, 10, 20, 50, ... 遞增的數值來(lái)計算合適的循環(huán)次數,直到總計時(shí)間至少為 0.2 秒。

default_timer() 測量可能受到在同一臺機器上運行的其他程序的影響,因此在需要精確計時(shí)時(shí)最好的做法是重復幾次計時(shí)并使用最佳時(shí)間。 -r 選項對此有利;在大多數情況下,默認的 5 次重復可能就足夠了。 你可以使用 time.process_time() 來(lái)測量CPU時(shí)間。

備注

執行 pass 語(yǔ)句會(huì )產(chǎn)生一定的基線(xiàn)開(kāi)銷(xiāo)。這里的代碼不會(huì )試圖隱藏它,但你應該知道它??梢酝ㄟ^(guò)不帶參數調用程序來(lái)測量基線(xiàn)開(kāi)銷(xiāo),并且Python版本之間可能會(huì )有所不同。

例子?

可以提供一個(gè)在開(kāi)頭只執行一次的 setup 語(yǔ)句:

$ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
1000000 loops, best of 5: 0.342 usec per loop

In the output, there are three fields. The loop count, which tells you how many times the statement body was run per timing loop repetition. The repetition count ('best of 5') which tells you how many times the timing loop was repeated, and finally the time the statement body took on average within the best repetition of the timing loop. That is, the time the fastest repetition took divided by the loop count.

>>>
>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

使用 Timer 類(lèi)及其方法可以完成同樣的操作:

>>>
>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

以下示例顯示如何計算包含多行的表達式。 在這里我們對比使用 hasattr()try/except 的開(kāi)銷(xiāo)來(lái)測試缺失與提供對象屬性:

$ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 5: 2.23 usec per loop
>>>
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

要讓 timeit 模塊訪(fǎng)問(wèn)你定義的函數,你可以傳遞一個(gè)包含 import 語(yǔ)句的 setup 參數:

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

另一種選擇是將 globals() 傳遞給 globals 參數,這將導致代碼在當前的全局命名空間中執行。這比單獨指定 import 更方便

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))