[解決!Python]timeitモジュールで実行時間を計測するには:解決!Python
timeitモジュールのtimeit関数は小規模なコードの実行時間を計測するのに使える。その基本的な使い方を紹介する。
# 基本型
import timeit
# 実行するPythonコード(文)を文字列として用意しておく
bycomp = '"".join([chr(c) for c in range(ord("a"), ord("a")+26)])'
bymap = '"".join(map(chr, range(ord("a"), ord("a")+26)))'
# Pythonの文を文字列として渡す
t = timeit.timeit(bycomp)
print(t) # 1.6198786449967884など
t = timeit.timeit(bymap)
print(t) # 1.2530384509882424など
# セットアップで実行する文(第1引数)の準備をする
from math import sqrt
t = timeit.timeit('sqrt(2)') # NameError:timeitの名前空間ではsqrtが未定義
t = timeit.timeit('sqrt(2)', 'from math import sqrt')
t = timeit.timeit('a + b', 'a = 10; b = 20')
# setupで関数を定義して、stmtでその関数を呼び出す
deffunc = '''
def myfunc():
return "".join(map(chr, range(ord("a"), ord("a")+26)))
'''
callfunc = 'myfunc()'
t = timeit.timeit(callfunc, deffunc)
# 名前空間を指定する
def foo():
return "".join(map(chr, range(ord("a"), ord("a")+26)))
timeit.timeit('foo()') # NameError:timeitモジュール内で未定義のため
timeit.timeit('foo()', globals=globals()) # 現在のグローバル名前空間を渡す
# 呼び出し可能オブジェクト(引数なし)を渡す
timeit.timeit(foo)
# 実行回数を指定する
t = timeit.timeit(bymap, number=100000)
# タイマーの種類を変更する
import time
t = timeit.timeit(bymap, timer=time.process_time)
# timeit.timeit関数を複数回呼び出す
t = timeit.repeat('"".join([chr(c) for c in range(ord("a"), ord("a")+26)])')
print(t)
# 出力結果
# [1.572056960008922, 1.5784977519942913, 1.5998643350030761, 1.5844960929971421, 1.5959375229867874]
timeitモジュール
Pythonに標準で添付のtimeitモジュールは規模の小さなコードの実行時間を計測するのに役立つ。コマンドラインツールとしても使えるが、本稿ではPythonコードとしてこのモジュールを使って実行時間を計測する方法を紹介する。
timeitモジュールが提供するtimeit関数の構文を以下に示す。
timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
パラメーターの意味は以下の通りだ。
- stmt:実行時間を計測したいPythonコード(文)
- setup:stmtを実行できるようにするための準備(文)
- timer:実行時間の計測に使用するタイマーの指定
- number:stmtを実行する回数
- globals:stmtを実行する名前空間の指定
例えば、'a'から'z'の各文字を連結するには幾つかの方法があるだろう。そこで、ここではリスト内包表記を使う方法とmap関数を使う方法の実行時間を計測したいとする。それらのコードをまず文字列として用意しておく。
bycomp = '"".join([chr(c) for c in range(ord("a"), ord("a")+26)])'
bymap = '"".join(map(chr, range(ord("a"), ord("a")+26)))'
文字列としたPythonコードをtimeit.timeit関数に渡せば、それをnumber回だけ実行し、それにかかった時間が返される。
t = timeit.timeit(bycomp)
print(t) # 1.6198786449967884など
t = timeit.timeit(bymap)
print(t) # 1.2530384509882424など
この結果からはmap関数を使った方が速そうなことが分かる。
このように、ちょっとしたコードの実行時間を手軽に計測するのにtimeit.timeit関数は便利に使える。
その一方で注意すべき点も幾つかある。まず、stmtやsetupに引き渡されたコードは特に指定をしない限り、timeitモジュールの名前空間内で実行されることだ。
例えば、mathモジュールのsqrt関数の実行時間を計測したいとする。このときに以下のようなコードを書いてもNameError例外が発生する。
from math import sqrt
t = timeit.timeit('sqrt(2)') # NameError:timeitの名前空間ではsqrtが未定義
「from math import sqrt」として現在の名前空間にsqrt関数をインポートしても、デフォルトではtimeitモジュールの名前空間でコードが実行されるので、意味がないのだ。
sqrt関数を実行できるようにするには、setupでtimeitモジュールの名前空間にsqrt関数をインポートすることが考えられる。これを行っているのが以下だ。
t = timeit.timeit('sqrt(2)', 'from math import sqrt')
このように、stmtには実行時間を計測したいコードを、setupにはそのために必要な準備(セットアップ)を行うコードを記述する。setupに書いたコードは実行時間の計測対象とはならない。
なお、stmt(計測対象のコード)とsetup(セットアップ用のコード)はセミコロン「;」や改行を含む複数の文で構成されていてもよい。
以下に例を示す。
t = timeit.timeit('a + b', 'a = 10; b = 20')
これはsetupで2つの変数を定義して、stmtではそれらを加算するようにしている。あるいは次のようなコードも考えられるだろう。
deffunc = '''
def myfunc():
return "".join(map(chr, range(ord("a"), ord("a")+26)))
'''
callfunc = 'myfunc()'
t = timeit.timeit(callfunc, deffunc)
これはsetupには関数を定義するコードを渡して、stmtにはそれを呼び出すコードを渡している。
setupに実行時間を計測した関数を渡すのは一つの解決策だが、globalsに名前空間を渡すことでstmtに渡したコードを実行できるようにもできる。
例えば、現在の名前空間で以下の関数を定義しているとする。
def foo():
return "".join(map(chr, range(ord("a"), ord("a")+26)))
しかし、これはtimeitモジュールの名前空間では定義されていないので、以下のように呼び出そうとするとNameError例外が発生する。
timeit.timeit('foo()') # NameError:timeitモジュール内で未定義のため
以下のように現在の名前空間をglobals関数で取得して、それをtimeit.timeit関数に渡すことで、この関数を実行できる。
timeit.timeit('foo()', globals=globals()) # 現在のグローバル名前空間を渡す
あるいは関数そのものをtimeit.timeit関数に渡すことも考えられる。stmtとsetupには引数を持たない関数オブジェクトを渡すことも可能だ。
timeit.timeit(foo)
「foo」の前後にクオートがない(つまり、関数オブジェクトである)こと、「foo()」のように関数を呼び出した結果を渡しているわけではない点に注意しよう。
timeit.timeit関数はデフォルトでstmtに渡されたコードを100万回実行するのにかかる時間を計測する。しかし、これが多すぎるまたは少なすぎるときにはnumberパラメーターに実行回数を指定できる。以下に例を示す。
t = timeit.timeit(bymap, number=100000)
この例では実行回数を10万回に指定している(そのため、実行にかかる時間はおおよそ10分の1となっているだろう)。
timeit.timeit関数はデフォルトでtimeモジュールのperf_counter関数をタイマーとして使用するが、これを変更したいときにはtimerパラメーターに望みのものを指定すればよい。以下は、time.process_time関数を渡す例だ。
import time
t = timeit.timeit(bymap, timer=time.process_time)
timeit.timeit関数の呼び出しを何度か行って、その計測時間がおおよそ一定の値かどうか、大きく外れた値がないかを確認したいこともある。そのときにはtimeit.repeat関数を使うとよい。これはtimeit.timeit関数を複数回(デフォルトでは5回)自動的に呼び出してくれるものだ。戻り値は計測結果を要素とするリストとなる。
以下に例を示す。
t = timeit.repeat('"".join([chr(c) for c in range(ord("a"), ord("a")+26)])')
print(t)
# 出力結果の例:
# [1.572056960008922, 1.5784977519942913, 1.5998643350030761, 1.5844960929971421, 1.5959375229867874]
Copyright© Digital Advantage Corp. All Rights Reserved.