timeit --- 測(cè)量小代碼片段的執(zhí)行時(shí)間?
源碼: Lib/timeit.py
該模塊提供了一種簡(jiǎn)單的方法來(lái)計(jì)算一小段 Python 代碼的耗時(shí)。它有 命令行界面 以及一個(gè) 可調(diào)用 方法。它避免了許多用于測(cè)量執(zhí)行時(shí)間的常見(jiàn)陷阱。另見(jiàn) Tim Peters 對(duì) O'Reilly 出版的 Python Cookbook 中“算法”章節(jié)的介紹。
基本示例?
以下示例顯示了如何使用 命令行界面 來(lái)比較三個(gè)不同的表達(dá)式:
$ 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í)現(xiàn)
>>> 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è)可調(diào)用對(duì)象:
>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678
但請(qǐng)注意 timeit() 僅在使用命令行界面時(shí)會(huì)自動(dòng)確定重復(fù)次數(shù)。 在 例子 一節(jié)你可以找到更多的進(jìn)階示例。
Python 接口?
該模塊定義了三個(gè)便利函數(shù)和一個(gè)公共類:
-
timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)? 使用給定語(yǔ)句、 setup 代碼和 timer 函數(shù)創(chuàng)建一個(gè)
Timer實(shí)例,并執(zhí)行 number 次其timeit()方法。可選的 globals 參數(shù)指定用于執(zhí)行代碼的命名空間。在 3.5 版更改: 添加可選參數(shù) globals 。
-
timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)? 使用給定語(yǔ)句、 setup 代碼和 timer 函數(shù)創(chuàng)建一個(gè)
Timer實(shí)例,并使用給定的 repeat 計(jì)數(shù)和 number 執(zhí)行運(yùn)行其repeat()方法。可選的 globals 參數(shù)指定用于執(zhí)行代碼的命名空間。在 3.5 版更改: 添加可選參數(shù) globals 。
在 3.7 版更改: repeat 的默認(rèn)值由 3 更改為 5 。
-
timeit.default_timer()? 默認(rèn)的計(jì)時(shí)器,總是
time.perf_counter()。在 3.3 版更改:
time.perf_counter()現(xiàn)在是默認(rèn)計(jì)時(shí)器。
-
class
timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)? 用于小代碼片段的計(jì)數(shù)執(zhí)行速度的類。
構(gòu)造函數(shù)接受一個(gè)將計(jì)時(shí)的語(yǔ)句、一個(gè)用于設(shè)置的附加語(yǔ)句和一個(gè)定時(shí)器函數(shù)。兩個(gè)語(yǔ)句都默認(rèn)為
'pass';計(jì)時(shí)器函數(shù)與平臺(tái)有關(guān)(請(qǐng)參閱模塊文檔字符串)。 stmt 和 setup 也可能包含多個(gè)以;或換行符分隔的語(yǔ)句,只要它們不包含多行字符串文字即可。該語(yǔ)句默認(rèn)在 timeit 的命名空間內(nèi)執(zhí)行;可以通過(guò)將命名空間傳遞給 globals 來(lái)控制此行為。要測(cè)量第一個(gè)語(yǔ)句的執(zhí)行時(shí)間,請(qǐng)使用
timeit()方法。repeat()和autorange()方法是方便的方法來(lái)調(diào)用timeit()多次。setup 的執(zhí)行時(shí)間從總體計(jì)時(shí)執(zhí)行中排除。
stmt 和 setup 參數(shù)也可以使用不帶參數(shù)的可調(diào)用對(duì)象。這將在一個(gè)計(jì)時(shí)器函數(shù)中嵌入對(duì)它們的調(diào)用,然后由
timeit()執(zhí)行。請(qǐng)注意,由于額外的函數(shù)調(diào)用,在這種情況下,計(jì)時(shí)開(kāi)銷會(huì)略大一些。在 3.5 版更改: 添加可選參數(shù) globals 。
-
timeit(number=1000000)? 執(zhí)行 number 次主要語(yǔ)句。這將執(zhí)行一次 setup 語(yǔ)句,然后返回執(zhí)行主語(yǔ)句多次所需的時(shí)間,以秒為單位測(cè)量為浮點(diǎn)數(shù)。參數(shù)是通過(guò)循環(huán)的次數(shù),默認(rèn)為一百萬(wàn)。要使用的主語(yǔ)句、 setup 語(yǔ)句和 timer 函數(shù)將傳遞給構(gòu)造函數(shù)。
注解
默認(rèn)情況下,
timeit()暫時(shí)關(guān)閉 garbage collection 。這種方法的優(yōu)點(diǎn)在于它使獨(dú)立時(shí)序更具可比性。缺點(diǎn)是GC可能是所測(cè)量功能性能的重要組成部分。如果是這樣,可以在 setup 字符串中的第一個(gè)語(yǔ)句重新啟用GC。例如:timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
-
autorange(callback=None)? 自動(dòng)決定調(diào)用多少次
timeit()。這是一個(gè)便利函數(shù),它反復(fù)調(diào)用
timeit(),以便總時(shí)間 >= 0.2 秒,返回最終(循環(huán)次數(shù),循環(huán)所用的時(shí)間)。它調(diào)用timeit()的次數(shù)以序列 1, 2, 5, 10, 20, 50, ... 遞增,直到所用的時(shí)間至少為0.2秒。如果給出 callback 并且不是
None,則在每次試驗(yàn)后將使用兩個(gè)參數(shù)調(diào)用它:callback(number, time_taken)。3.6 新版功能.
-
repeat(repeat=5, number=1000000)? 調(diào)用
timeit()幾次。這是一個(gè)方便的函數(shù),它反復(fù)調(diào)用
timeit(),返回結(jié)果列表。第一個(gè)參數(shù)指定調(diào)用timeit()的次數(shù)。第二個(gè)參數(shù)指定timeit()的 number 參數(shù)。注解
從結(jié)果向量計(jì)算并報(bào)告平均值和標(biāo)準(zhǔn)差這些是很誘人的。但是,這不是很有用。在典型情況下,最低值給出了機(jī)器運(yùn)行給定代碼段的速度的下限;結(jié)果向量中較高的值通常不是由Python的速度變化引起的,而是由于其他過(guò)程干擾你的計(jì)時(shí)準(zhǔn)確性。所以結(jié)果的
min()可能是你應(yīng)該感興趣的唯一數(shù)字。之后,你應(yīng)該看看整個(gè)向量并應(yīng)用常識(shí)而不是統(tǒng)計(jì)。在 3.7 版更改: repeat 的默認(rèn)值由 3 更改為 5 。
-
print_exc(file=None)? 幫助程序從計(jì)時(shí)代碼中打印回溯。
典型使用:
t = Timer(...) # outside the try/except try: t.timeit(...) # or t.repeat(...) except Exception: t.print_exc()
與標(biāo)準(zhǔn)回溯相比,優(yōu)勢(shì)在于將顯示已編譯模板中的源行。可選的 file 參數(shù)指向發(fā)送回溯的位置;它默認(rèn)為
sys.stderr。
-
命令行界面?
從命令行調(diào)用程序時(shí),使用以下表單:
python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]
如果了解以下選項(xiàng):
-
-nN,--number=N? 執(zhí)行 '語(yǔ)句' 多少次
-
-rN,--repeat=N? 重復(fù)計(jì)時(shí)器的次數(shù)(默認(rèn)為5)
-
-sS,--setup=S? 最初要執(zhí)行一次的語(yǔ)句(默認(rèn)為
pass)
-
-p,--process? 測(cè)量進(jìn)程時(shí)間,而不是 wallclock 時(shí)間,使用
time.process_time()而不是time.perf_counter(),這是默認(rèn)值3.3 新版功能.
-
-u,--unit=U? 指定定時(shí)器輸出的時(shí)間單位;可以選擇 nsec,usec,msec或sec
3.5 新版功能.
-
-v,--verbose? 打印原始計(jì)時(shí)結(jié)果;重復(fù)更多位數(shù)精度
-
-h,--help? 打印一條簡(jiǎn)短的使用信息并退出
可以通過(guò)將每一行指定為單獨(dú)的語(yǔ)句參數(shù)來(lái)給出多行語(yǔ)句;通過(guò)在引號(hào)中包含參數(shù)并使用前導(dǎo)空格可以縮進(jìn)行。多個(gè) -s 選項(xiàng)的處理方式相似。
如果未給出 -n,則會(huì)通過(guò)嘗試按序列 1, 2, 5, 10, 20, 50, ... 遞增的數(shù)值來(lái)計(jì)算合適的循環(huán)次數(shù),直到總計(jì)時(shí)間至少為 0.2 秒。
default_timer() 測(cè)量可能受到在同一臺(tái)機(jī)器上運(yùn)行的其他程序的影響,因此在需要精確計(jì)時(shí)時(shí)最好的做法是重復(fù)幾次計(jì)時(shí)并使用最佳時(shí)間。 -r 選項(xiàng)對(duì)此有利;在大多數(shù)情況下,默認(rèn)的 5 次重復(fù)可能就足夠了。 你可以使用 time.process_time() 來(lái)測(cè)量CPU時(shí)間。
注解
執(zhí)行 pass 語(yǔ)句會(huì)產(chǎn)生一定的基線開(kāi)銷。這里的代碼不會(huì)試圖隱藏它,但你應(yīng)該知道它。可以通過(guò)不帶參數(shù)調(diào)用程序來(lái)測(cè)量基線開(kāi)銷,并且Python版本之間可能會(huì)有所不同。
例子?
可以提供一個(gè)在開(kāi)頭只執(zhí)行一次的 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
>>> 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 類及其方法可以完成同樣的操作:
>>> 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]
以下示例顯示如何計(jì)算包含多行的表達(dá)式。 在這里我們對(duì)比使用 hasattr() 與 try/except 的開(kāi)銷來(lái)測(cè)試缺失與提供對(duì)象屬性:
$ 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 模塊訪問(wèn)你定義的函數(shù),你可以傳遞一個(gè)包含 import 語(yǔ)句的 setup 參數(shù):
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 參數(shù),這將導(dǎo)致代碼在當(dāng)前的全局命名空間中執(zhí)行。這比單獨(dú)指定 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()))
