関数の実行時間計測

関数の実行時間を計測する場合は、セットアップ文に、"form __main__ import 関数名"を指定します。そうしないと、timeitモジュールが関数にアクセスできないようです。

from timeit import Timer

def range_test():
    n = 0
    for i in range(1000): n += 1

t1 = Timer("range_test()", "from __main__ import range_test")

print t1.timeit(10000)

うおっ!関数をそのままtimeitモジュールで計測できるの知らなかった。

でも誰も触れていないということは結構知られていない気がする。timeitモジュールのここら辺の仕組みは難しいなあ。後でEffective Pythonに追記しておこう…。


追記:

timeit.pyのソースを読んで理解できた。要はTimer(stmt="pass", setup="pass", timer=default_timer)で、単にsetupを1回とstmtを指定した回数実行して時間を計測しているだけだった。つまり、stmtもしくは、setupに関数の定義を渡してあげないと関数が呼び出せないということだった。

def foo(func):
    import timeit
    t1 = timeit.Timer('Timer.__dict__["r"] = %s()' % func.__name__, 'from %s import %s' % (__name__, func.__name__))
    print t1.timeit(1), 'sec elapsed'
    return timeit.Timer.__dict__['r']

少し汎用性のあるように関数を作成してみた。上記のfoo()に関数オブジェクトを引数で渡すと、渡された関数を無引数で呼び出した際の実行時間が表示される。おまけとして、戻り値も返すようにした。


stmtの実行はTimer#inner()で行っている。inner()の定義は、stmtとsetupを埋め込んだinner()関数を文字列で定義して、それをビルトインのcompile()関数でコード化してexecで実行した結果(つまりinner()関数を定義した結果)をself.innerに代入してインスタンスメソッドとしている。ここは説明よりもコードを直接見た方が分かりやすい。


戻り値は、stmtの実行結果はinner()関数内で捨てている。Pythonでは文は戻り値を戻さないのである意味当たり前。そこで、stmt自体に戻り値を"どこかに"保存しておく処理を書くしかない。そこで、Timerクラスの__dic__に保存して後で取り出している。


しかし、時間計測は、結局win32であれば、time.clock()の戻り値の差、それ以外であれば、time.time()の戻り値の差を返しているだけなので、関数呼び出しの戻り値を返したいなら自分でその処理を書いた方が簡単である。よって、エラー処理を考慮しないのであれば、どう書く?orgに書いた自分の方法でデコレータを実現する方法で問題ない。time.timeよりもtime.clockを使った方が良かったという程度。