パフォーマンス計測 in Python

timeモジュール(time.perf_counter)

  • 使いどころは特定のコードブロックに対する計測。精度はマイクロ秒程度。精度を求める場合はこれ。(逆にcProfile/line_profileなどのプロファイリングは基本的にクロック程度の精度,1/100秒程度しかなく,ボトルネックの発見が目的と考えてよいのかもしれない。)
  • 使い方は開始位置と終了位置でtime.time()ならエポック秒(UNIX時間)を,time.perf_counter()ならパフォーマンスカウンタの値を取得して差分として計算する。
  • Pythonのドキュメントに依ると,time.time()システムによっては1秒程度しか精度が無い場合もある。しかし,UNIXにおいてはgettimeofday()を使って時刻を取得するようなので,マイクロ秒単位程度の精度があるようだ。 # Web上にはtime.time()が1/60~1/100秒程度の精度の記述が見られるが,Python3のドキュメントにはその記述は見つけられなかった。
  • より明確にパフォーマンスカウンタ値を返す関数としてtime.perf_counter()がある。time.time()と同じような気もするけど,使い方は同じであるし,無難にこちらを使っておく。 # 実際に,time.get_clock_info('time')とtime.get_clock_info('perf_counter')のresolutionはどちらも1e-09。
import time
start_time = time.perf_counter()
# some heavy calculation
# ...
end_time =  time.perf_counter()
print('Elapsed time:{:.7}'.format(end_time - start_time))

cProfile

  • 使いどころは関数ごとの処理時間やコール回数を計測したい場合。精度は1/100秒程度。
  • 使い方はスクリプト実行時に,cProfileをインポートして実行するだけ。
  • 表示の意味をまとめておく。
    • ncalls: 呼び出し回数。再帰の場合が別途表示されていて,"再帰含む場合"/”再帰を含まない場合”の2つが出力される。
    • tottime: その関数で消費された時間。その関数が呼び出す関数の消費時間は含まない。
    • cumtime: その関数で消費された時間。その関数が呼び出す関数の消費時間は含む。
  • '-s'オプションでソートして出力することがほとんど。
    • time: tottimeでソート
    • cumulative: cumtimeでソート
    • calls: ncallsでソート
# tak.py (Takeuchi Function) is tested as an example.
hoge@foo:~/$ python3 -m cProfile -s time tak.py 

         12604868 function calls (8 primitive calls) in 3.457 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
12604861/1    3.457    0.000    3.457    3.457 tak.py:3(tak)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    3.457    3.457 tak.py:15(main)
        1    0.000    0.000    3.457    3.457 tak.py:1(<module>)
        2    0.000    0.000    0.000    0.000 {built-in method time.perf_counter}
        1    0.000    0.000    3.457    3.457 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

line_profiler

  • 使いどころは行単位のプロファイルを取りたい場合。cProfileであたりを関数単位で付けて,計測したい関数をline_profileで指定(デコレータ)する。
  • いくつかの使い方があるが,コードを修正してしまうので面倒ではあるが単純なのは関数に@profileデコレータを付けて,kerfnprof.py(line_profileと一緒にインストールされる)を使う。
# hoge.py
@profile
def f():
    # heavy calculation    
    ...
    ...
    ...
    return 
  • pipでインストールして,かつline_profilerをgit cloneしていて例があるが,kernprof.py自体はローカルにあるので不要。
$ pip3 install line_profiler --user # ~/.local/lib/python3.6/site-packages/にインストールされる。
$ python3 ~/.local/lib/python3.6/site-packages/kernprof.py -l -v hoge.py