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

源碼: Lib/timeit.py


此模塊提供了一種簡(jiǎn)單的方法來(lái)計(jì)算一小段 Python 代碼的耗時(shí)。 它有 命令行接口 以及一個(gè) 可調(diào)用 方法。 它避免了許多測(cè)量時(shí)間的常見(jiàn)陷阱。 另見(jiàn) Tim Peter 在 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() 方法??蛇x的 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() 方法??蛇x的 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)參閱模塊文檔字符串)。 stmtsetup 也可能包含多個(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í)行中排除。

stmtsetup 參數(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ì)在于將顯示已編譯模板中的源行??蛇x的 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):

-n N, --number=N?

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

-r N, --repeat=N?

重復(fù)計(jì)時(shí)器的次數(shù)(默認(rèn)為5)

-s S, --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?

specify a time unit for timer output; can select nsec, usec, msec, or 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

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 類及其方法可以完成同樣的操作:

>>>
>>> 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()))