26.4。 Pythonプロファイラー
ソースコード: :source: `Lib / profile.py` and :source: `Lib / pstats.py`
26.4.1。 プロファイラーの概要
cProfile および profile は、Pythonプログラムの決定論的プロファイリングを提供します。 プロファイルは、プログラムのさまざまな部分が実行された頻度と期間を説明する一連の統計です。 これらの統計は、 pstats モジュールを介してレポートにフォーマットできます。
Python標準ライブラリは、同じプロファイリングインターフェイスの3つの異なる実装を提供します。
cProfile はほとんどのユーザーに推奨されます。 これは、長時間実行されるプログラムのプロファイリングに適した、妥当なオーバーヘッドを備えたC拡張機能です。
lsprof
に基づいており、BrettRosenとTedCzotterによって寄稿されました。バージョン2.5の新機能。
profile は、インターフェイスが cProfile によって模倣されているが、プロファイルされたプログラムにかなりのオーバーヘッドを追加する純粋なPythonモジュールです。 プロファイラーを何らかの方法で拡張しようとしている場合は、このモジュールを使用するとタスクが簡単になる可能性があります。 もともとはジム・ロスキンドによって設計および作成されました。
バージョン2.4で変更:組み込み関数およびメソッドの呼び出しに費やされた時間も報告するようになりました。
hotshot は、データの後処理時間が長くなる代わりに、プロファイリングのオーバーヘッドを最小限に抑えることに焦点を当てた実験的なCモジュールでした。 それはもはや維持されておらず、Pythonの将来のバージョンで削除される可能性があります。
バージョン2.5での変更:結果は以前よりも意味のあるものになるはずです。タイミングコアに重大なバグが含まれていました。
profile モジュールと cProfile モジュールは同じインターフェイスをエクスポートするため、ほとんど互換性があります。 cProfile のオーバーヘッドははるかに低くなりますが、新しく、すべてのシステムで使用できるとは限りません。 cProfile は、実際には内部_lsprof
モジュールの上にある互換性レイヤーです。 hotshot モジュールは、特殊な使用のために予約されています。
ノート
プロファイラーモジュールは、ベンチマークの目的ではなく、特定のプログラムの実行プロファイルを提供するように設計されています(そのため、適度に正確な結果を得るために timeit があります)。 これは特に、PythonコードをCコードに対してベンチマークする場合に当てはまります。プロファイラーはPythonコードにオーバーヘッドを導入しますが、Cレベルの関数にはオーバーヘッドを導入しないため、CコードはどのPythonコードよりも高速に見えます。
26.4.2。 インスタントユーザーズマニュアル
このセクションは、「マニュアルを読みたくない」ユーザー向けに提供されています。 非常に簡単な概要を提供し、ユーザーが既存のアプリケーションでプロファイリングを迅速に実行できるようにします。
単一の引数を取る関数をプロファイリングするには、次の操作を実行できます。
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
(システムで cProfile が使用できない場合は、 cProfile の代わりに profile を使用してください。)
上記のアクションは re.compile()を実行し、次のようなプロファイル結果を出力します。
197 function calls (192 primitive calls) in 0.002 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 re.py:212(compile)
1 0.000 0.000 0.001 0.001 re.py:268(_compile)
1 0.000 0.000 0.000 0.000 sre_compile.py:172(_compile_charset)
1 0.000 0.000 0.000 0.000 sre_compile.py:201(_optimize_charset)
4 0.000 0.000 0.000 0.000 sre_compile.py:25(_identityfunction)
3/1 0.000 0.000 0.000 0.000 sre_compile.py:33(_compile)
最初の行は、197件のコールが監視されたことを示しています。 これらの呼び出しのうち、192はプリミティブでした。これは、呼び出しが再帰によって誘導されなかったことを意味します。 次の行:Ordered by: standard name
は、右端の列のテキスト文字列が出力の並べ替えに使用されたことを示します。 列見出しは次のとおりです。
- ncalls
- 呼び出し回数については、
- tottime
- 特定の関数で費やされた合計時間(およびサブ関数の呼び出しで行われた時間を除く)
- パーコール
tottime
をncalls
で割った商です。- cumtime
- これとすべてのサブ機能(呼び出しから終了まで)に費やされた累積時間です。 この数値は、再帰関数の場合はでもでも正確です。
- パーコール
cumtime
をプリミティブ呼び出しで割った商です- ファイル名:lineno(関数)
- 各機能のそれぞれのデータを提供します
最初の列に2つの数字がある場合(たとえば、3/1
)、関数が繰り返されたことを意味します。 2番目の値はプリミティブ呼び出しの数であり、前者は呼び出しの総数です。 関数が繰り返されない場合、これら2つの値は同じであり、1つの図のみが出力されることに注意してください。
プロファイルの実行の最後に出力を出力する代わりに、run()
関数にファイル名を指定することにより、結果をファイルに保存できます。
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')
pstats.Stats クラスは、ファイルからプロファイル結果を読み取り、さまざまな方法でフォーマットします。
ファイル cProfile は、別のスクリプトをプロファイルするためのスクリプトとして呼び出すこともできます。 例えば:
python -m cProfile [-o output_file] [-s sort_order] myscript.py
-o
は、プロファイル結果をstdoutではなくファイルに書き込みます
-s
は、 sort_stats()ソート値の1つを指定して、出力をソートします。 これは、-o
が指定されていない場合にのみ適用されます。
pstats モジュールの Stats クラスには、プロファイル結果ファイルに保存されたデータを操作および印刷するためのさまざまなメソッドがあります。
import pstats
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()
strip_dirs()メソッドは、すべてのモジュール名から無関係なパスを削除しました。 sort_stats()メソッドは、出力される標準のモジュール/行/名前の文字列に従ってすべてのエントリを並べ替えました。 print_stats()メソッドは、すべての統計を出力しました。 次の並べ替え呼び出しを試すことができます。
p.sort_stats('name')
p.print_stats()
最初の呼び出しは実際に関数名でリストをソートし、2番目の呼び出しは統計を出力します。 以下は、実験するためのいくつかの興味深い呼び出しです。
p.sort_stats('cumulative').print_stats(10)
これにより、関数内の累積時間でプロファイルが並べ替えられ、最も重要な10行のみが出力されます。 どのアルゴリズムに時間がかかっているかを理解したい場合は、上記の行を使用します。
どの関数が多くループしていて、多くの時間がかかっているかを確認したい場合は、次のようにします。
p.sort_stats('time').print_stats(10)
各関数内で費やされた時間に従ってソートし、上位10個の関数の統計を出力します。
あなたも試してみてください:
p.sort_stats('file').print_stats('__init__')
これにより、すべての統計がファイル名で並べ替えられ、クラスのinitメソッドのみの統計が出力されます(__init__
が含まれているため)。 最後の例として、次のことを試すことができます。
p.sort_stats('time', 'cum').print_stats(.5, 'init')
この行は、時間の主キーと累積時間の副キーを使用して統計をソートし、統計の一部を出力します。 具体的には、リストは最初に元のサイズの50%(re:.5
)にカリングされ、次にinit
を含む行のみが維持され、そのサブサブリストが出力されます。 。
上記の関数と呼ばれる関数が何か疑問に思った場合は、次のことができます(p
は引き続き最後の基準に従ってソートされます)。
p.print_callers(.5, 'init')
リストされた各関数の呼び出し元のリストを取得します。
より多くの機能が必要な場合は、マニュアルを読むか、次の機能が何をするかを推測する必要があります。
p.print_callees()
p.add('restats')
スクリプトとして呼び出される pstats モジュールは、プロファイルダンプを読み取って調べるための統計ブラウザです。 シンプルなライン指向のインターフェース( cmd を使用して実装)とインタラクティブなヘルプがあります。
26.4.3。 プロフィールと cProfile モジュールリファレンス
profile モジュールと cProfile モジュールはどちらも、次の機能を提供します。
- profile.run(command, filename=None, sort=- 1)
この関数は、
exec()
関数に渡すことができる単一の引数と、オプションのファイル名を取ります。 すべての場合において、このルーチンは以下を実行します。exec(command, __main__.__dict__, __main__.__dict__)
実行からプロファイリング統計を収集します。 ファイル名が存在しない場合、この関数は自動的に Stats インスタンスを作成し、単純なプロファイリングレポートを出力します。 並べ替え値が指定されている場合は、この Stats インスタンスに渡され、結果の並べ替え方法が制御されます。
- profile.runctx(command, globals, locals, filename=None)
この関数は run()に似ていますが、コマンド文字列のグローバル辞書とローカル辞書を提供する引数が追加されています。 このルーチンは以下を実行します。
exec(command, globals, locals)
上記の run()関数のようにプロファイリング統計を収集します。
- class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)
このクラスは通常、
cProfile.run()
関数が提供するものよりも正確なプロファイリングの制御が必要な場合にのみ使用されます。timer 引数を介して、コードの実行にかかる時間を測定するためのカスタムタイマーを提供できます。 これは、現在の時刻を表す単一の数値を返す関数である必要があります。 数値が整数の場合、 timeunit は、各時間単位の期間を指定する乗数を指定します。 たとえば、タイマーが数千秒単位で測定された時間を返す場合、時間の単位は
.001
になります。Profile クラスを直接使用すると、プロファイルデータをファイルに書き込まずにプロファイル結果をフォーマットできます。
import cProfile, pstats, StringIO pr = cProfile.Profile() pr.enable() # ... do something ... pr.disable() s = StringIO.StringIO() sortby = 'cumulative' ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() print s.getvalue()
- enable()
プロファイリングデータの収集を開始します。
- disable()
プロファイリングデータの収集を停止します。
- create_stats()
プロファイリングデータの収集を停止し、結果を現在のプロファイルとして内部的に記録します。
- print_stats(sort=- 1)
現在のプロファイルに基づいて Stats オブジェクトを作成し、結果をstdoutに出力します。
- dump_stats(filename)
現在のプロファイルの結果をファイル名に書き込みます。
- run(cmd)
exec()
を介してcmdをプロファイリングします。
- runctx(cmd, globals, locals)
指定されたグローバル環境とローカル環境で
exec()
を介してcmdのプロファイルを作成します。
- runcall(func, *args, **kwargs)
プロファイル
func(*args, **kwargs)
26.4.4。 NSStats クラス
プロファイラーデータの分析は、 Stats クラスを使用して行われます。
- class pstats.Stats(*filenames or profile, stream=sys.stdout)
このクラスコンストラクターは、ファイル名(またはファイル名のリスト)または
Profile
インスタンスから「統計オブジェクト」のインスタンスを作成します。 stream で指定されたストリームに出力が出力されます。上記のコンストラクターによって選択されたファイルは、対応するバージョンの profile または cProfile によって作成されている必要があります。 具体的には、このプロファイラーの将来のバージョンでは no ファイルの互換性が保証されておらず、他のプロファイラーによって作成されたファイル、または同じプロファイラーが別のオペレーティングシステムで実行されているファイルとの互換性はありません。 複数のファイルが提供されている場合、同一の機能のすべての統計が合体されるため、複数のプロセスの全体像を1つのレポートで検討できます。 追加のファイルを既存の Stats オブジェクトのデータと組み合わせる必要がある場合は、 add()メソッドを使用できます。
ファイルからプロファイルデータを読み取る代わりに、
cProfile.Profile
または profile.Profile オブジェクトをプロファイルデータソースとして使用できます。Stats オブジェクトには次のメソッドがあります。
- strip_dirs()
Stats クラスのこのメソッドは、ファイル名からすべての先行パス情報を削除します。 プリントアウトのサイズを80列(に近い)に収まるように縮小するのに非常に役立ちます。 このメソッドはオブジェクトを変更し、削除された情報は失われます。 ストリップ操作を実行した後、オブジェクトは、オブジェクトの初期化とロードの直後であったため、「ランダム」な順序でエントリを持っていると見なされます。 strip_dirs()によって2つの関数名が区別できなくなった場合(同じファイル名の同じ行にあり、同じ関数名を持っている)、これら2つのエントリの統計は1つのエントリに蓄積されます。 。
- add(*filenames)
Stats クラスのこのメソッドは、追加のプロファイリング情報を現在のプロファイリングオブジェクトに蓄積します。 その引数は、対応するバージョンの profile.run()または
cProfile.run()
によって作成されたファイル名を参照する必要があります。 同じ名前(ファイル、行、名前)の関数の統計は、単一の関数の統計に自動的に蓄積されます。
- dump_stats(filename)
Stats オブジェクトにロードされたデータを filename という名前のファイルに保存します。 ファイルが存在しない場合は作成され、すでに存在する場合は上書きされます。 これは、 profile.Profile クラスと
cProfile.Profile
クラスの同じ名前のメソッドと同等です。
バージョン2.3の新機能。
- sort_stats(*keys)
このメソッドは、 Stats オブジェクトを、指定された基準に従って並べ替えることによって変更します。 引数は通常、並べ替えの基準を識別する文字列です(例:
'time'
または'name'
)。複数のキーが提供されている場合、それらの前に選択されたすべてのキーが等しい場合、追加のキーが2次基準として使用されます。 たとえば、
sort_stats('name', 'file')
は、すべてのエントリを関数名に従って並べ替え、ファイル名で並べ替えることによってすべての関係(同一の関数名)を解決します。略語が明確である限り、任意のキー名に略語を使用できます。 現在定義されているキーは次のとおりです。
有効な引数
意味
'calls'
通話数
'cumulative'
累積時間
'cumtime'
累積時間
'file'
ファイル名
'filename'
ファイル名
'module'
ファイル名
'ncalls'
通話数
'pcalls'
プリミティブコールカウント
'line'
行番号
'name'
関数名
'nfl'
名前/ファイル/行
'stdname'
標準名
'time'
内部時間
'tottime'
内部時間
統計のすべての並べ替えは降順(最も時間のかかる項目を最初に配置)であるのに対し、名前、ファイル、および行番号の検索は昇順(アルファベット順)であることに注意してください。
'nfl'
と'stdname'
の微妙な違いは、標準名が印刷された名前の一種であるということです。つまり、埋め込まれた行番号が奇妙な方法で比較されます。 たとえば、行3、20、および40は、(ファイル名が同じである場合)文字列の順序20、3、および40で表示されます。 対照的に、'nfl'
は行番号の数値比較を行います。 実際、sort_stats('nfl')
はsort_stats('name', 'file', 'line')
と同じです。下位互換性の理由から、数値引数
-1
、0
、1
、および2
が許可されます。 それらは、それぞれ'stdname'
、'calls'
、'time'
、および'cumulative'
として解釈されます。 この古いスタイルの形式(数値)が使用される場合、1つのソートキー(数値キー)のみが使用され、追加の引数は黙って無視されます。
- reverse_order()
Stats クラスのこのメソッドは、オブジェクト内の基本リストの順序を逆にします。 デフォルトでは、選択したソートキーに基づいて、昇順と降順が適切に選択されていることに注意してください。
- print_stats(*restrictions)
Stats クラスのこのメソッドは、 profile.run()定義で説明されているようにレポートを出力します。
印刷の順序は、オブジェクトに対して実行された最後の sort_stats()操作に基づいています( add()および strip_dirs()の警告があります)。
提供された引数(存在する場合)を使用して、リストを重要なエントリに制限できます。 最初は、リストはプロファイルされた関数の完全なセットであると見なされます。 各制限は、整数(行数を選択するため)、0.0から1.0までの小数(行数のパーセンテージを選択するため)、または正規表現(印刷される標準名とパターン一致するため)のいずれかです。 複数の制限が提供されている場合、それらは順番に適用されます。 例えば:
print_stats(.1, 'foo:')
最初に印刷を最初の10 % o fリストに制限し、次にファイル名
.*foo:
の一部である関数のみを印刷します。 対照的に、コマンド:print_stats('foo:', .1)
リストをファイル名が
.*foo:
のすべての関数に制限し、最初の10 % o fのみを出力します。
- print_callers(*restrictions)
Stats クラスのこのメソッドは、プロファイルデータベース内の各関数を呼び出したすべての関数のリストを出力します。 順序は print_stats()によって提供されるものと同じであり、制限引数の定義も同じです。 各発信者は、独自の回線で報告されます。 形式は、統計を生成したプロファイラーによってわずかに異なります。
- print_callees(*restrictions)
Stats クラスのこのメソッドは、指定された関数によって呼び出されたすべての関数のリストを出力します。 この呼び出しの方向の反転(re:呼び出されたvsが呼び出された)を除けば、引数と順序は print_callers()メソッドと同じです。
26.4.5。 決定論的プロファイリングとは何ですか?
確定的プロファイリングは、すべての関数呼び出し、関数戻り、および例外イベントが監視され、正確なタイミングがこれらのイベント間の間隔(ユーザーのコードが実行されている時間)に対して作成されます。 対照的に、統計プロファイリング(このモジュールでは実行されません)は、有効な命令ポインターをランダムにサンプリングし、時間が費やされている場所を推測します。 後者の手法は、従来、オーバーヘッドが少なくて済みますが(コードをインストルメント化する必要がないため)、時間が費やされている場所の相対的な指標のみを提供します。
Pythonでは、実行中にアクティブなインタープリターがあるため、確定的なプロファイリングを行うためにインストルメント化されたコードが存在する必要はありません。 Pythonは、イベントごとにフック(オプションのコールバック)を自動的に提供します。 さらに、Pythonの解釈された性質は、実行に非常に多くのオーバーヘッドを追加する傾向があるため、決定論的プロファイリングは、一般的なアプリケーションでは小さな処理オーバーヘッドのみを追加する傾向があります。 その結果、決定論的プロファイリングはそれほど高価ではありませんが、Pythonプログラムの実行に関する広範な実行時統計を提供します。
呼び出し数の統計を使用して、コードのバグを特定し(驚くべき数)、インライン展開の可能性のあるポイントを特定することができます(呼び出し数が多い)。 内部時間統計を使用して、慎重に最適化する必要がある「ホットループ」を特定できます。 累積時間統計を使用して、アルゴリズムの選択における高レベルのエラーを特定する必要があります。 このプロファイラーでの累積時間の異常な処理により、アルゴリズムの再帰的実装の統計を反復実装と直接比較できることに注意してください。
26.4.6。 制限事項
1つの制限は、タイミング情報の精度に関係しています。 精度を含む決定論的プロファイラーには根本的な問題があります。 最も明白な制限は、基礎となる「時計」が(通常)約.001秒の速度でしか刻々と過ぎないことです。 したがって、基礎となる時計よりも正確な測定値はありません。 十分な測定が行われると、「エラー」は平均化される傾向があります。 残念ながら、この最初のエラーを削除すると、2番目のエラーの原因が発生します。
2番目の問題は、イベントがディスパッチされてから、実際に時刻を取得するためのプロファイラーの呼び出しが時計の状態を取得するまで「時間がかかる」ことです。 同様に、プロファイラーイベントハンドラーを終了するときは、クロックの値が取得されてから(そしてリスされて)、ユーザーのコードが再び実行されるまでに一定の遅延があります。 その結果、何度も呼び出されたり、多くの関数を呼び出したりする関数は、通常、このエラーを累積します。 この方法で蓄積されるエラーは、通常、クロックの精度よりも低くなります(1クロックティック未満)が、蓄積して非常に重要になる可能性があります。
この問題は、オーバーヘッドの低い cProfile よりも profile の方が重要です。 このため、 profile は、特定のプラットフォームに対してそれ自体を調整する手段を提供し、このエラーを確率的に(平均して)除去できるようにします。 プロファイラーがキャリブレーションされた後、それはより正確になります(最小二乗の意味で)が、負の数を生成することがあります(呼び出し数が非常に少なく、確率の神があなたに反対する場合:-)。 )プロファイル内の負の数によって警告されない。 プロファイラーをキャリブレーションした場合は、のみが表示されるはずであり、実際には、キャリブレーションを行わない場合よりも結果が良くなります。
26.4.7。 較正
profile モジュールのプロファイラーは、各イベント処理時間から定数を減算して、time関数を呼び出し、結果を吸収するオーバーヘッドを補正します。 デフォルトでは、定数は0です。 次の手順を使用して、特定のプラットフォームのより良い定数を取得できます(制限を参照)。
import profile
pr = profile.Profile()
for i in range(5):
print pr.calibrate(10000)
このメソッドは、引数で指定されたPython呼び出しの数をプロファイラーの下で直接実行し、両方の時間を測定します。 次に、プロファイラーイベントごとに非表示のオーバーヘッドを計算し、それをフロートとして返します。 たとえば、Mac OSXを実行している1.8GhzIntel Core i5で、タイマーとしてPythonのtime.clock()を使用すると、魔法の数は約4.04e-6になります。
この演習の目的は、かなり一貫した結果を得ることです。 コンピューターが非常に高速である場合、またはタイマー関数の解像度が低い場合、一貫した結果を得るには、100000、さらには1000000を渡す必要があります。
一貫した答えがある場合、それを使用する3つの方法があります: 1
import profile
# 1. Apply computed bias to all Profile instances created hereafter.
profile.Profile.bias = your_computed_bias
# 2. Apply computed bias to a specific Profile instance.
pr = profile.Profile()
pr.bias = your_computed_bias
# 3. Specify computed bias in instance constructor.
pr = profile.Profile(bias=your_computed_bias)
選択肢がある場合は、小さい定数を選択することをお勧めします。そうすると、結果がプロファイル統計で「ネガティブ」として表示されることが「少なくなります」。
26.4.8。 カスタムタイマーの使用
現在の時刻の決定方法を変更する場合(たとえば、実時間や経過処理時間を強制的に使用する場合)、必要なタイミング関数をProfile
クラスコンストラクターに渡します。
pr = profile.Profile(your_time_func)
結果のプロファイラーはyour_time_func
を呼び出します。 profile.Profile とcProfile.Profile
のどちらを使用しているかによって、your_time_func
の戻り値の解釈は異なります。
profile.Profile
your_time_func
は、単一の数値、または合計が現在の時刻である数値のリストを返す必要があります( os.times()が返すものなど)。 関数が単一の時間番号を返す場合、または返された番号のリストの長さが2の場合、ディスパッチルーチンの特に高速なバージョンを取得します。選択したタイマー関数のプロファイラークラスを調整する必要があることに注意してください(調整を参照)。 ほとんどのマシンでは、単独の整数値を返すタイマーが、プロファイリング中のオーバーヘッドが少ないという点で最良の結果を提供します。 ( os.times()は、浮動小数点値のタプルを返すため、かなり悪いです)。 よりクリーンな方法でより良いタイマーを置き換えたい場合は、クラスを導出し、適切なキャリブレーション定数とともに、タイマー呼び出しを最適に処理する置換ディスパッチメソッドを配線します。
cProfile.Profile
your_time_func
は単一の数値を返す必要があります。 整数を返す場合は、1単位時間の実際の期間を指定する2番目の引数を使用してクラスコンストラクターを呼び出すこともできます。 たとえば、your_integer_time_func
が数千秒単位で測定された時間を返す場合、Profile
インスタンスを次のように作成します。pr = cProfile.Profile(your_integer_time_func, 0.001)
cProfile.Profile
クラスは調整できないため、カスタムタイマー関数は注意して使用し、可能な限り高速にする必要があります。 カスタムタイマーで最良の結果を得るには、内部_lsprof
モジュールのCソースにタイマーをハードコーディングする必要がある場合があります。
脚注
- 1
- Python 2.2より前は、バイアスをリテラル数として埋め込むためにプロファイラーのソースコードを編集する必要がありました。 それでも可能ですが、その方法は不要になったため、説明されていません。