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

源碼: Lib/timeit.py


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

基本示例?

以下示例顯示了如何使用 命令行接口 來比較三個不同的表達(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

這可以通過 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 接口 還可以傳出一個可調(diào)用對象:

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

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

Python 接口?

該模塊定義了三個便利函數(shù)和一個公共類:

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

使用給定語句、 setup 代碼和 timer 函數(shù)創(chuàng)建一個 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)?

使用給定語句、 setup 代碼和 timer 函數(shù)創(chuàng)建一個 Timer 實(shí)例,并使用給定的 repeat 計數(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)的計時器,總是 time.perf_counter() 。

在 3.3 版更改: time.perf_counter() 現(xiàn)在是默認(rèn)計時器。

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

用于小代碼片段的計數(shù)執(zhí)行速度的類。

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

要測量第一個語句的執(zhí)行時間,請使用 timeit() 方法。 repeat()autorange() 方法是方便的方法來調(diào)用 timeit() 多次。

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

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

在 3.5 版更改: 添加可選參數(shù) globals 。

timeit(number=1000000)?

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

備注

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

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

自動決定調(diào)用多少次 timeit() 。

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

如果給出 callback 并且不是 None ,則在每次試驗(yàn)后將使用兩個參數(shù)調(diào)用它: callback(number, time_taken)

3.6 新版功能.

repeat(repeat=5, number=1000000)?

調(diào)用 timeit() 幾次。

這是一個方便的函數(shù),它反復(fù)調(diào)用 timeit() ,返回結(jié)果列表。第一個參數(shù)指定調(diào)用 timeit() 的次數(shù)。第二個參數(shù)指定 timeit()number 參數(shù)。

備注

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

在 3.7 版更改: repeat 的默認(rèn)值由 3 更改為 5 。

print_exc(file=None)?

幫助程序從計時代碼中打印回溯。

典型使用:

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

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

命令行接口?

從命令行調(diào)用程序時,使用以下表單:

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

如果了解以下選項(xiàng):

-n N, --number=N?

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

-r N, --repeat=N?

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

-s S, --setup=S?

最初要執(zhí)行一次的語句(默認(rèn)為 pass

-p, --process?

測量進(jìn)程時間,而不是 wallclock 時間,使用 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?

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

-h, --help?

打印一條簡短的使用信息并退出

可以通過將每一行指定為單獨(dú)的語句參數(shù)來給出多行語句;通過在引號中包含參數(shù)并使用前導(dǎo)空格可以縮進(jìn)行。多個 -s 選項(xiàng)的處理方式相似。

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

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

備注

執(zhí)行 pass 語句會產(chǎn)生一定的基線開銷。這里的代碼不會試圖隱藏它,但你應(yīng)該知道它??梢酝ㄟ^不帶參數(shù)調(diào)用程序來測量基線開銷,并且Python版本之間可能會有所不同。

例子?

可以提供一個在開頭只執(zhí)行一次的 setup 語句:

$ 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]

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

$ 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 模塊訪問你定義的函數(shù),你可以傳遞一個包含 import 語句的 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()))