1. 程式人生 > >timeit 模組詳解(準確測量小段程式碼的執行時間)

timeit 模組詳解(準確測量小段程式碼的執行時間)

timeit 模組詳解 -- 準確測量小段程式碼的執行時間

timeit 模組提供了測量 Python 小段程式碼執行時間的方法。它既可以在命令列介面直接使用,也可以通過匯入模組進行呼叫。該模組靈活地避開了測量執行時間所容易出現的錯誤。

以下例子是命令列介面的使用方法:

$ python -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 40.3 usec per loop
$ python -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 33.4 usec per loop
$ python -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 25.2 usec per loop

以下例子是 IDLE 下呼叫的方法:

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.8187260627746582
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.7288308143615723
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.5858950614929199

需要注意的是,只有當使用命令列介面時,timeit 才會自動確定重複的次數。

timeit 模組

該模組定義了三個實用函式和一個公共類。

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

建立一個 Timer 例項,引數分別是 stmt(需要測量的語句或函式),setup(初始化程式碼或構建環境的匯入語句),timer(計時函式),number(每一次測量中語句被執行的次數)

注:由於 timeit() 正在執行語句,語句中如果存在返回值的話會阻止 timeit() 返回執行時間。timeit() 會取代原語句中的返回值。



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

建立一個 Timer 例項,引數分別是 stmt(需要測量的語句或函式),setup(初始化程式碼或構建環境的匯入語句),timer(計時函式),repeat(重複測量的次數),number(每一次測量中語句被執行的次數)


timeit.default_timer()

預設的計時器,一般是 time.perf_counter(),time.perf_counter() 方法能夠在任一平臺提供最高精度的計時器(它也只是記錄了自然時間,記錄自然時間會被很多其他因素影響,例如計算機的負載)。


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

計算小段程式碼執行速度的類,建構函式需要的引數有 stmt(需要測量的語句或函式),setup(初始化程式碼或構建環境的匯入語句),timer(計時函式)。前兩個引數的預設值都是 'pass',timer 引數是平臺相關的;前兩個引數都可以包含多個語句,多個語句間使用分號(;)或新行分隔開。

第一次測試語句的時間,可以使用 timeit() 方法;repeat() 方法相當於持續多次呼叫 timeit() 方法並將結果返回為一個列表。

stmt 和 setup 引數也可以是可供呼叫但沒有引數的物件,這將會在一個計時函式中巢狀呼叫它們,然後被 timeit() 所執行。注意,由於額外的呼叫,計時開銷會相對略到。


- timeit(number=1000000) 

功能:計算語句執行 number 次的時間。

它會先執行一次 setup 引數的語句,然後計算 stmt 引數的語句執行 number 次的時間,返回值是以秒為單位的浮點數。number 引數的預設值是一百萬,stmt、setup 和 timer 引數由 timeit.Timer 類的建構函式傳遞。

注意:預設情況下,timeit() 在計時的時候會暫時關閉 Python 的垃圾回收機制。這樣做的優點是計時結果更具有可比性,但缺點是 GC(garbage collection,垃圾回收機制的縮寫)有時候是測量函式效能的一個重要組成部分。如果是這樣的話,GC 可以在 setup 引數執行第一條語句的時候被重新啟動,例如:

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

- repeat(repeat=3, number=1000000) 

功能:重複呼叫 timeit()。

repeat() 方法相當於持續多次呼叫 timeit() 方法並將結果返回為一個列表。repeat 引數指定重複的次數,number 引數傳遞給 timeit() 方法的 number 引數。

注意:人們很容易計算出平均值和標準偏差,但這並不是非常有用。在典型的情況下,最低值取決於你的機器可以多快地執行給定的程式碼段;在結果中更高的那些值通常不是由於 Python 的速度導致,而是因為其他程序干擾了你的計時精度。所以,你所應感興趣的只有結果的最低值(可以用 min() 求出)。


- print_exc(file=None) 

功能:輸出計時程式碼的回溯(Traceback)

典型的用法:

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

標準回溯的優點是在編譯模板中,源語句行會被顯示出來。可選的 file 引數指定將回溯傳送的位置,預設是傳送到 sys.stderr。


命令列介面

當被作為命令列程式呼叫時,可以使用下列選項:

python -m timeit [-n N] [-r N] [-s S] [-t] [-c] [-h] [statement ...]

各個選項的含義:
 

選項 原型 含義
-n N --number=N 執行指定語句(段)的次數
-r N --repeat=N 重複測量的次數(預設 3 次)
-s S --setup=S 指定初始化程式碼或構建環境的匯入語句(預設是 pass)
-p --process 測量程序時間而不是實際執行時間(使用 time.process_time() 代替預設的 time.perf_counter())
以下是 Python3.3 新增:    
-t --time 使用 time.time()(不推薦)
-c --clock 使用 time.clock()(不推薦)
-v --verbose 列印原始的計時結果,輸出更大精度的數值
-h --help 列印一個簡短的用法資訊並退出


示例

以下演示如果在開始的時候設定初始化語句:

命令列:

$ python -m timeit -s 'text = "I love FishC.com!"; char = "o"'  'char in text'
10000000 loops, best of 3: 0.0877 usec per loop
$ python -m timeit -s 'text = "I love FishC.com!"; char = "o"'  'text.find(char)'
1000000 loops, best of 3: 0.342 usec per loop


使用 timeit 模組:

>>> import timeit
>>> timeit.timeit('char in text', setup='text = "I love FishC.com!"; char = "o"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "I love FishC.com!"; char = "o"')
1.7246671520006203

使用 Timer 物件:

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "I love FishC.com!"; char = "o"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40193588800002544, 0.3960157959998014, 0.39594301399984033]


以下演示包含多行語句如何進行測量:

(我們通過 hasattr() 和 try/except 兩種方法測試屬性是否存在,並且比較它們之間的效率)

命令列:

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

$ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
1000000 loops, best of 3: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 3: 2.23 usec per loop

使用 timeit 模組:

>>> 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 模組可以測量你的函式,你可以在 setup 引數中通過 import 語句匯入:
 

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

附上 timeti 模組的實現原始碼,小甲魚強烈建議有時間的朋友可以研究一下(這對你的程式設計能力大有裨益):