timeit --- 小さなコードスニペットの実行時間計測¶
ソースコード: Lib/timeit.py
このモジュールは、小さな Python コードの実行時間を簡単に計測する手段を提供します。 コマンドライン・インターフェース と、Python から呼び出せるインターフェース の両方があります。 実行時間の計測で陥りがちな問題の多くを回避できます。 O'Reilly 刊 Python Cookbook 第 2 版の「Algorithms」章にある Tim Peters による導入も参照してください。
基本的な例¶
次の例は コマンドライン・インターフェース を使って 3 つの異なる式の時間を測定する方法を示しています。
$ python -m timeit "'-'.join(str(n) for n in range(100))"
10000 loops, best of 5: 30.2 usec per loop
$ python -m timeit "'-'.join([str(n) for n in range(100)])"
10000 loops, best of 5: 27.5 usec per loop
$ python -m timeit "'-'.join(map(str, range(100)))"
10000 loops, best of 5: 23.2 usec per loop
同じ事を Python インターフェース を使って実現することもできます:
>>> 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 インターフェース から渡すこともできます:
>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678
ただし、timeit() はコマンドラインインターフェイスを使った時だけ繰り返し回数を自動で決定する事に注意してください。使用例 節でより高度な例を説明しています。
Python インターフェース¶
このモジュールは 3 つの有用な関数と 1 つの公開クラスを持っています:
- timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)¶
指定された文、setup コード、timer 関数で
Timerインスタンスを作成し、timeit()メソッドでその文を number 回実行する時間を計測します。 オプションの globals 引数には、コードを実行する名前空間を指定します。バージョン 3.5 で変更: 任意の globals 引数が追加されました。
- timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)¶
指定された文、setup コード、timer 関数で
Timerインスタンスを作成し、repeat()メソッドを実行します。 文を number 回実行する計測を repeat 回繰り返します。 オプションの globals 引数には、コードを実行する名前空間を指定します。バージョン 3.5 で変更: 任意の globals 引数が追加されました。
バージョン 3.7 で変更: repeat のデフォルト値が 3 から 5 へ変更されました。
- timeit.default_timer()¶
浮動小数点数の秒数を返すデフォルトのタイマーで、常に time.perf_counter() でとなります。代わりに、 time.perf_counter_ns は整数のナノ秒を返します。
バージョン 3.3 で変更: デフォルトのタイマーが
time.perf_counter()になりました。
- class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)¶
小さなコード片の実行時間を計測するためのクラスです。
コンストラクターは計測する文、セットアップのための追加の文、ならびにタイマー関数を取ります。 両文のデフォルトは
'pass'です。 タイマー関数はプラットフォーム依存です (モジュールの doctring を参照)。 stmt および setup には、複数行の文字列リテラルを含まない限り、;や改行で区切られた複数の文でも構いません。 デフォルトでは文は timeit の名前空間内で実行されます。 この挙動は名前空間を globals に渡すことで制御出来ます。最初の命令文の実行時間を計測するには、
timeit()メソッドを使用します。repeat()とautorange()メソッドはtimeit()を複数回呼び出したい時に使用します。setup の実行時間は全実行時間から除外されています。
stmt および setup パラメータは、引数なしの呼び出し可能オブジェクトをとることもできます。呼び出し可能オブジェクトを指定すると、そのオブジェクトの呼び出しがタイマー関数に埋め込まれ、その関数が
timeit()メソッドによって実行されます。この場合、関数呼び出しが増えるためにタイミングのオーバーヘッドが少し増える点に注意してください。バージョン 3.5 で変更: 任意の globals 引数が追加されました。
- timeit(number=1000000)¶
メイン文を number 回実行した時間を計測します。このメソッドはセットアップ文を1回だけ実行し、メイン文を指定回数実行するのにかかった時間を返します。デフォルトのタイマーは秒数を浮動小数点数で返します。引数はループを何回実行するかの指定で、デフォルト値は 100万回です。メイン文、セットアップ文、タイマー関数はコンストラクターで指定されたものを使用します。
注釈
デフォルトでは、
timeit()は計測中、一時的に ガベージコレクション を停止します。 この手法の利点は個々の計測結果がより比較しやすくなることです。 欠点は、ガベージコレクションが計測される関数の性能の重要な要素である場合があることです。 その場合、setup 文字列の最初の文でガベージコレクションを有効にできます。 以下に例を示します:timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
- autorange(callback=None, target_time=None)¶
timeit()を呼び出す回数を自動的に決定します。This is a convenience function that calls
timeit()repeatedly so that the total time >= Timer.target_time seconds, returning the eventual (number of loops, time taken for that number of loops). It callstimeit()with increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the time taken is at least target_time seconds.callback が与えられ、Noneでない場合は、 callback(number, time_taken) という2つの引数を指定して試行された後に呼び出されます。
Added in version 3.6.
バージョン 3.15 で変更: 省略可能な target_time 引数が追加されました。
- repeat(repeat=5, number=1000000)¶
timeit()を複数回繰り返します。これは
timeit()を繰り返し呼び出したい時に有用で、結果をリストにして返します。最初の引数で何回timeit()を呼ぶか指定します。第 2 引数でtimeit()の引数 number を指定します。注釈
結果のベクトルから平均値や標準偏差を計算して出力させたいと思うかもしれませんが、それはあまり意味がありません。多くの場合、最も低い値がそのマシンが与えられたコード断片を実行する場合の下限値です。結果のうち高めの値は、Python のスピードが一定しないために生じたものではなく、その他の計測精度に影響を及ぼすプロセスによるものです。したがって、結果のうち
min()だけが見るべき値となるでしょう。この点を押さえた上で、統計的な分析よりも常識的な判断で結果を見るようにしてください。バージョン 3.7 で変更: repeat のデフォルト値が 3 から 5 へ変更されました。
- print_exc(file=None)¶
計測対象コードのトレースバックを出力するためのヘルパーです。
利用例:
t = Timer(...) # try/except の外側 try: t.timeit(...) # または t.repeat(...) except Exception: t.print_exc()
これの標準のトレースバックより優れた点は、コンパイルしたテンプレートのソース行が表示されることです。オプションの引数 file にはトレースバックの出力先を指定します。デフォルトは
sys.stderrになります。
コマンドライン・インターフェース¶
コマンドラインからプログラムとして呼び出す場合は、次の書式を使います:
python -m timeit [-n N] [-r N] [-u U] [-s S] [-p] [-v] [-h] [statement ...]
以下のオプションが使用できます:
- -n N, --number=N¶
'statement' を実行する回数
- -r N, --repeat=N¶
timer を繰り返す回数(デフォルトは5)
- -s S, --setup=S¶
最初に1回だけ実行する文 (デフォルトは
pass)
- -p, --process¶
デフォルトの
time.perf_counter()の代わりtime.process_time()を利用して、実時間ではなくプロセス時間を計測しますAdded in version 3.3.
- -u, --unit=U¶
timer 出力の時間の単位を指定します。
nsec、usec、msec、secを選択できます。Added in version 3.5.
- -t, --target-time=T¶
--numberが 0 の場合、実行に少なくともこの秒数がかかるまでコードを実行する (デフォルト: 0.2)Added in version 3.15.
- -v, --verbose¶
計測結果の生の値を表示します。 このオプションを繰り返し指定すると表示の桁数が増えます。
- -h, --help¶
簡単な使い方を表示して終了する
文は複数行指定することもできます。その場合、各行は独立した文として引数に指定されたものとして処理します。クォートと行頭のスペースを使って、インデントした文を使うことも可能です。この複数行のオプションは -s においても同じ形式で指定可能です。
-n を指定しなかった場合は、1、2、5、10、20、50、... と回数を増やして試し、合計時間が少なくとも --target-time 秒 (デフォルト: 0.2) となる適切なループ回数を求めます。
default_timer() の測定値は、同じマシン上で実行されている他のプログラムの影響を受ける可能性があるため、正確な時間計測が必要な場合は、計測を数回繰り返し、最適な時間を使用することをおすすめします。 -r オプションはこれに適しています。ほとんどの場合、デフォルトの5回の繰り返しで十分でしょう。 time.process_time() を使用すればCPU時間を測定できます。
注釈
pass 文の実行には基本的なオーバーヘッドが存在します。ここにあるコードはこの事実を隠そうとはしていませんが、注意する必要があります。基本的なオーバーヘッドは引数なしでプログラムを起動することにより計測でき、それは Python のバージョンによって異なるでしょう。
Added in version 3.15: デフォルトでは色付きで出力します。 環境変数で制御できます。
使用例¶
最初に 1 回だけ実行されるセットアップ文を指定することが可能です:
$ 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
出力には、3つのフィールドがあります。ループ回数は、ループの反復ごとに何回文の内容が実行されたかを示します。反復回数 ('best of 5') は計時ループが何回繰り返されたかを示し、最後に文の内容が平均して計時ループの中の最良の反復で要した時間を示します。つまり、最速の反復をループ回数で割った時間です。
>>> 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]
以下の例は、複数行を含んだ式を計測する方法を示しています。ここでは、オブジェクトの存在する属性と存在しない属性に対してテストするために 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
>>> # 属性が存在しない場合
>>> 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
>>>
>>> # 属性が存在する場合
>>> 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 引数を渡します。
def test():
"""単純なテスト関数"""
L = [i for i in range(100)]
if __name__ == '__main__':
import timeit
print(timeit.timeit("test()", setup="from __main__ import test"))
もう 1 つの方法は、globals 引数に globals() の戻り値を渡すことです。
これにより、現在のグローバル名前空間内でコードを実行できます。
インポートする対象を 1 つずつ指定するより便利な場合があります。
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()))