Pythonのプロファイラモジュールあれこれ plus あるふぁ

Pythonで小さなツールを作っている際には、速度の問題というのはあまり 気にならないかもしれません。ソーストレースでも処理時間が掛かっていそうな ところがおおよそ検討がつき、すぐに修正もできます。 それでも勘に頼った修正や解析は泥沼にはまることもありますし、ましてや 規模の大きなファイルのボトルネックを探すとなるとソーストレースでは 限界があります。 このような場面ではプロファイラを使用するのがベターです。

Pythonのプロファイラには3つのモジュールが用意されています。 cProfileモジュールhotshotモジュールtimeitモジュール です。

hotshot

hotshotモジュールは2.2から使用できるプロファイルモジュールで、 初期のプロファイルモジュールであるprofileモジュールよりオーバヘッドが 少ないようです。 (profileモジュールはGuidoが3週間ほどで実装したPurePythonのモジュールです。) Python3.0がリリースされている現在、profileのサンプルコードを 示すのはやめておきます。ちょっとめんどくさい。そもそも使わないし。

以下、hotshotのサンプルコードです。

import hotshot
import hotshot.stats
import test.pystone

prof = hotshot.Profile("hot.prof")

benchtime, stones = prof.runcall(test.pystone.pystones)
prof.close()

s = hotshot.stats.load("hot.prof")
s.strip_dirs()
s.sort_stats('time', 'calls')
s.print_stats(30)

名前はhotshot(名手)ですが、バージョン2.5以前のhotshotは時間計測の 中核処理に致命的なバグがあるようです。

またhotshotが吐くプロファイルダンプデータ(測定情報のバイナリデータ)は 非常に巨大です。上記例のhot.profファイルは約4Mbyteありました。 後述のcProfileで吐き出されるダンプデータは4Kbyteです。ディスク容量が テラバイトなんてのも珍しくない時代になりましたが、 同じ情報を出力するのであれば小さいに越したことはないです。

また公式ドキュメントによると今後標準ライブラリから外される可能性が あるとのことですので、使用機会としては限られたものになるでしょう。

cProfile

cProfileはprofileモジュールをCで実装しなおしたモジュールで、 profileモジュールと互換性があります。profileモジュールよりも プロファイリングすることによるオーバヘッドが少ないので、 2.5以降でcProfileを使わない理由はありません。

以下、サンプルコードです。

import test.pystone
import cProfile
import pstats

prof = cProfile.run("test.pystone.pystones()", 'cprof.prof')
p = pstats.Stats('cprof.prof')
p.strip_dirs()
p.sort_stats('time', 'calls')
p.print_stats()

timeit

timeitはコード断片の時間測定用モジュールです。バージョン2.3から使えます。 それほど大きくないコードを測定するのに使います。コマンドラインからの使用も可能で、 思いついた実装が複数あった場合などに測定してから実装する場面などで使えそうです。 xrange()とrange()などの既知(?)の優位性を判断するのではなく、 もう少し込み入っているコードで使用すると良いのではないでしょうか。 良い例が思い浮かばなかったので、xrange()とrange()の例にしていますが。。。

$ python -m timeit "for x in range(100):pass"
100000 loops, best of 3: 7.2 usec per loop
import timeit

stx = """
def life():
    for x in xrange(10000**2):
        pass
"""
st = """
def life():
    for x in range(10000**2):
        pass
"""

tr = timeit.Timer(st)
tx = timeit.Timer(stx)
print tr.timeit()
print tx.timeit()

print tr.repeat()
print tx.repeat()

ベンチマーク用途で使用するのが一般的だと思います。 私自身はあまりtimeitを使ったことがありません。 ですのでtimeitモジュールの底力を引き出せていないかもしれません。

いったんまとめ

timeitを除くプロファイルモジュールのバージョンによる違いを まとめてみました。以下のような感じでしょうか。

mod/ver ~2.1 2.2~2.4 2.5~
profile ×
hotshot
cProfile

ぷらすあるふぁ(pyprof2html)

http://images-jp.amazon.com/images/P/4798119172.09.THZZZZZZZ.jpg

プロファイルのことを書いている時に、つい最近購入した「モダンPerl入門」 をパラパラっとページをめくっていると、目を引くスクリーンショットが。 それはPerlの Devel::NYTProf で出力されるHTMLで、出力結果が非常にCool。 これをPythonでもできないかなと思い PYPI を検索してみたのですが、 適当なものが見つからなかったので、自分で書いてみました。

pyprof2html

cProfileかhotshotが出力したプロファイルダンプをもとに、HTMLにいい感じに出力します。 HTMLのテンプレートエンジンに Jinja2 を使っていますので、 追加でインストールが必要です。 まだまだ改善の余地は多いですが、よければ使ってみてください。

余談ですが、モダンPerl入門は私の中で Perlプログラミング救命病棟 に並ぶ良書だと感じました。