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 模組的實現原始碼,小甲魚強烈建議有時間的朋友可以研究一下(這對你的程式設計能力大有裨益):