プロファむラヌはRubyずPythonでどのように機胜したすか

蚘事の翻蚳は、䞊玚コヌス「PythonDeveloper」の開始を芋越しお䜜成されたした。



元の蚘事はここで読むこずができたす。










こんにちはRubyプロファむラヌの付属品ずしお、既存のRubyおよびPythonプロファむラヌがどのように機胜するかに぀いお話したいず思いたした。たた、倚くの人が私に尋ねる「プロファむラヌの曞き方」ずいう質問に答えるのにも圹立ちたす。



この蚘事では、プロセッサプロファむラヌに焊点を圓おたすたずえば、メモリ/ヒヌププロファむラヌではありたせん。プロファむラヌを䜜成するためのいく぀かの基本的なアプロヌチをカバヌし、いく぀かのコヌド䟋を提䟛し、RubyずPythonで人気のあるプロファむラヌの倚くの䟋を瀺し、それらが内郚でどのように機胜するかを瀺したす。



おそらく蚘事に誀りがあるかもしれたせんそれを曞く準備ずしお、私はプロファむリングのために14のラむブラリのコヌドを郚分的に調べたした、そしお私は今たでそれらの倚くに粟通しおいたせんでした、それでそれらを芋぀けたら私に知らせおください..。



2皮類のプロファむラヌ



プロセッサプロファむラには、サンプリングプロファむラずトレヌスプロファむラの2぀の䞻芁なタむプがありたす。



トレヌスプロファむラヌは、プログラム内のすべおの関数呌び出しを蚘録し、最終的にレポヌトを提䟛したす。サンプリングプロファむラヌは統蚈的アプロヌチを取り、数ミリ秒ごずにスタックを曞き蟌み、このデヌタに基づいおレポヌトを生成したす。



トレヌスプロファむラヌの代わりにサンプリングプロファむラヌを䜿甚する䞻な理由は、軜量であるためです。あなたは毎秒20たたは200枚の写真を撮りたす-それはそれほど時間はかかりたせん。このようなプロファむラヌは、深刻なパフォヌマンスの問題がある堎合時間の80が1぀の遅い関数の呌び出しに費やされおいるに非垞に効果的です。問題の関数を特定するには、1秒あたり200のスナップショットで十分だからです。



プロファむラヌ



次に、この蚘事で説明したプロファむラヌの抂芁を説明したすここから。この蚘事で䜿甚されおいる甚語setitimer、rb_add_event_hook、ptraceに぀いおは埌で説明したす。興味深いこずに、すべおのプロファむラヌは、基本的な機胜の小さなセットを䜿甚しお実装されおいたす。



Pythonプロファむラヌ







「Gdbハック」は実際にはPythonプロファむラヌではありたせん。ハッカヌプロファむラヌをgdbのシェルスクリプトラッパヌずしお実装する方法を説明するWebサむトにリンクしおいたす。新しいバヌゞョンのgbdは実際にPythonスタックをデプロむするため、これは特にPythonに関するものです。貧しい人々のためのpyflameのようなもの。



Rubyプロファむラヌ







これらのプロファむラヌのほずんどすべおがプロセス内に存圚したすこれらのプロファむラヌ



の詳现に入る前に、非垞に重芁なこずが1぀ありたす。pyflameを陀くこれらのプロファむラヌはすべお、Python / Rubyプロセス内で実行されたす。Python / Rubyプログラム内にいる堎合は、通垞、スタックに簡単にアクセスできたす。たずえば、実行䞭の各スレッドのスタックの内容を出力する単玔なPythonプログラムを次に瀺したす。



import sys
import traceback

def bar():
    foo()

def foo():
    for _, frame in sys._current_frames().items():
        for line in traceback.extract_stack(frame):
            print line

bar()


これがコン゜ヌル出力です。スタックからの関数名、行番号、ファむル名など、プロファむリングする堎合に必芁なものがすべお含たれおいるこずがわかりたす。



('test2.py', 12, '<module>', 'bar()')
('test2.py', 5, 'bar', 'foo()')
('test2.py', 9, 'foo', 'for line in traceback.extract_stack(frame):')


これはRubyでさえ簡単ですあなたが䜿甚するこずができ、発信者プットをスタックを取埗したす。



これらのプロファむラヌのほずんどはCのパフォヌマンス拡匵機胜であるため、わずかに異なりたすが、Ruby / Pythonプログラム甚のこのような拡匵機胜は、呌び出しスタックにも簡単にアクセスできたす。



トレヌスプロファむラヌのしくみ



䞊蚘の衚に、 すべおのRubyおよびPythonトレヌスプロファむルをリストしたしたrblineprof、ruby-prof、line_profiler、およびcProfile。それらはすべお同じように機胜したす。これらはすべおの関数呌び出しを蚘録し、オヌバヌヘッドを削枛するためのC拡匵機胜です。



それらはどのように機胜したすか RubyずPythonの䞡方で、さたざたなむンタヌプリタヌむベント「関数呌び出し」や「コヌド実行行」などが発生したずきにトリガヌされるコヌルバックを指定できたす。コヌルバックが呌び出されるず、埌で分析するためにスタックが曞き蟌たれたす。



これらのコヌルバックがコヌド内のどこにあるかを正確に確認するず圹立぀ので、githubの関連するコヌド行にリンクしたす。



Pythonでは、PyEval_SetTraceたたはを䜿甚しおコヌルバックをカスタマむズできたすPyEval_SetProfile。これはドキュメントセクションで説明されおいたすPythonでのプロファむリングずトレヌス。「トレヌス機胜が行番号むベントを受信するこずPyEval_SetTraceをPyEval_SetProfile陀いお、同様です」ず衚瀺されたす。



コヌド



  • line_profilerを䜿甚しおコヌルバックを蚭定したすPyEval_SetTrace157line_profiler.pyx行目を参照
  • cProfileを䜿甚しおコヌルバックを蚭定したすPyEval_SetProfile_lsprof.c 693行目を参照cProfileはlsprofを䜿甚しお実装されたす


Rubyでは、を䜿甚しおコヌルバックをカスタマむズできたすrb_add_event_hook。それに関するドキュメントは芋぀かりたせんでしたが、このように芋えたす。



rb_add_event_hook(prof_event_hook,
      RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
      RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN |
      RUBY_EVENT_LINE, self);


眲名prof_event_hook



static void
prof_event_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)




PyEval_SetTracePythonず 䌌おいたすが、より柔軟な圢匏です。通知するむベントを遞択できたすたずえば、「関数呌び出しのみ」。



コヌド



  • ruby-prof rb_add_event_hook : ruby-prof.c 329
  • rblineprof rb_add_event_hook : rblineprof.c 649


tracing-



この方法で実装されたトレヌスプロファむラヌの䞻な欠点は、実行される関数/行呌び出しごずに䞀定量のコヌドが远加されるこずです。それはあなたに間違った決定をさせる可胜性がありたすたずえば、䜕かの実装が2぀ある堎合1぀は関数呌び出しが倚いもの、もう1぀はないもので、通垞は同じ時間がかかりたす、プロファむリング時に、関数呌び出しが倚い最初の実装は遅く衚瀺されたす。



説明のためにtest.py、次の内容で名前を付けた小さなファむルを䜜成し、実行時間python -mcProfile test.pyずを比范したしたpython test.py。python. test.py箄0.6秒ずpython -mcProfile test.py箄1秒で完了したした。したがっお、この特定の䟋ではcProfile、玄60のオヌバヌヘッドを远加したした。

ドキュメントにcProfileよるず、

Pythonの解釈された性質により、実行時のオヌバヌヘッドが非垞に倧きくなるため、決定論的プロファむリングでは、通垞のアプリケヌションで凊理のオヌバヌヘッドが少し増える傟向がありたす。


これはかなり合理的なステヌトメントのようです。前の䟋350䞇回の関数呌び出しのみを行うは明らかに通垞のPythonプログラムではなく、他のほずんどのプログラムのオヌバヌヘッドは少なくなりたす。Rubyトレヌスプロファむラヌを

チェックしおいたせんruby-profが、そのREADMEには次のように曞かれおいたす。

ほずんどのプログラムの実行速床は玄半分ですが、再垰性の高いプログラムFibonacciシリヌズテストなどの実行速床は3倍遅くなりたす。


サンプリングプロファむラヌの䞀般的な仕組みsetitimer



2番目の皮類のプロファむラヌに぀いお話す時間ですプロファむラヌのサンプリング

RubyおよびPythonのほずんどのサンプリングプロファむラヌは、システム呌び出しを䜿甚しお実装されたすsetitimer。それは䜕ですか



プログラムスタックのスナップショットを1秒間に50回取埗するずしたす。これは次のように実行できたす。



  • Linuxカヌネルに20ミリ秒ごずに信号を送信するように䟝頌したすシステム呌び出しを䜿甚setitimer。
  • 信号を受信したずきに、スタックスナップショットの信号ハンドラヌを登録したす。
  • プロファむリングが完了したら、Linuxにシグナリングを停止しお結果を提䟛するように䟝頌しおください。


setitimerサンプリングプロファむラヌを実装するための 実際の䜿甚䟋を芋たい堎合、stacksampler.py最良の䟋は䟿利で機胜するプロファむラヌであり、Pythonでは玄100行であるず思いたす。そしお、これはずおもクヌルですPythonで100行しかかからない



理由stacksampler.pyは、Python関数をシグナルハンドラヌずしお登録するず、その関数がプログラムの珟圚のスタックに枡されるためです。したがっお、シグナルハンドラヌのstacksampler.py登録は非垞に簡単です。



def _sample(self, signum, frame):
   stack = []
   while frame is not None:
       stack.append(self._format_frame(frame))
       frame = frame.f_back

   stack = ';'.join(reversed(stack))
   self._stack_counts[stack] += 1


フレヌムからスタックをポップし、特定のスタックが衚瀺された回数をむンクリメントするだけです。ずおも簡単ずおもクヌル



圌らが䜿甚しおいる他のすべおのプロファむラヌをsetitimer芋お、コヌドのどこで呌び出しおいるかを調べおみたしょうsetitimer。



  • stackprof (Ruby): stackprof.c 118
  • perftools.rb (Ruby): , , , , gem (?)
  • stacksampler (Python): stacksampler.py 51
  • statprof (Python): statprof.py 239
  • vmprof (Python): vmprof_unix.c 294


に぀いおの重芁なこずsetitimer-あなたは時間を数える方法を決める必芁がありたす。 20msのリアルタむムが必芁ですか 20msのナヌザヌcpu時間 20msナヌザヌ+システムcpu時間䞊蚘のリンクをよく芋るず、これらのプロファむラヌが実際には異なるものを䜿甚しおいるこずsetitimerがわかりたす。動䜜がカスタマむズ可胜な堎合ずそうでない堎合がありたす。マニュアルペヌゞはsetitimer短く、考えられるすべおの構成に぀いお読む䟡倀がありたす。Twitterで1぀の興味深いナヌスケヌス



@mgedminを指摘したしたsetitimer。この問題ずこの問題は詳现を明らかにしたす。



に基づくプロファむラヌの1぀の興味深い欠点setitimer-どのタむマヌが信号をトリガヌしたすか信号がシステム呌び出しを䞭断するこずがありたすシステム呌び出しには数ミリ秒かかる堎合がありたす。スナップショットを頻繁に䜜成する堎合は、プログラムにシステム呌び出しを無期限に実行させるこずができたす。



setitimerを䜿甚しないサンプリングプロファむラヌ



を䜿甚しないサンプリングプロファむラヌがいく぀かありたすsetitimer。



  • pyinstrumentを䜿甚したすPyEval_SetProfile぀たり、䞀皮のトレヌスプロファむラヌですが、トレヌスコヌルバックが呌び出されたずきにスタックスナップショットを垞に収集するずは限りたせん。スタックトレヌススナップショットのタむミングを遞択するコヌドは次のずおりです。このブログでこの゜リュヌションの詳现を読んでください。基本的にsetitimerPythonでメむンスレッドのみをプロファむリングできたす
  • pyflameシステム呌び出しを䜿甚しお、プロセスの倖郚でPythonコヌドをプロファむルしたすptrace。圌はルヌプを䜿甚しお写真を撮り、䞀定時間眠り、同じこずを繰り返したす。これが埅぀ための呌びかけです。
  • python-flamegraph同様のアプロヌチを取り、Pythonプロセスで新しいスレッドを開始し、スタックトレヌスを取埗し、スリヌプしお、最初からやり盎したす。これが埅機の呌び出しです。


これら3぀のプロファむラヌはすべお、リアルタむムのスナップショットを取埗したす。



Pyflameのブログ投皿



この投皿では、ほずんどすべおの時間を以倖のプロファむラヌに費やしたしたpyflameが、Pythonプログラムを別のプロセスからプロファむリングするため、実際に最も興味を持っおいたす。そのため、Rubyプロファむラヌを同様に機胜させたいず考えおいたす。



もちろん、すべおが私が説明したよりも少し耇雑です。詳现に぀いおは説明したせんが、EvanKlitzkeは圌のブログにこれに関する倚くの優れた蚘事を曞いおいたす。





詳现に぀いおは、eklitzke.orgをご芧ください。これらはすべお、私がこれから詳しく読む非垞に興味深いものです。おそらく、Rubyプロファむラヌを実装するptraceよりも優れおいるこずがprocess_vm_readvわかりたす。process_vm_readvプロセスを停止しないためオヌバヌヘッドが少なくなりたすが、プロセスを停止しないため、誀ったスナップショットが衚瀺される可胜性もありたす:)。私の実隓では、矛盟する画像を取埗するこずは倧きな問題ではありたせんでしたが、ここで䞀連の実隓を行うず思いたす。



それが今日のすべおです



この投皿では觊れなかった重芁な埮劙な点がたくさんありたす。たずえば、基本的にずは䌌おいるvmprofず蚀いたしたstacksamplerそうではありたせん。vmprof文字列プロファむリングずCで蚘述されたPython関数のプロファむリングをサポヌトしおいるため、プロファむラヌがより耇雑になるず思いたす。しかし、それらは同じ基本原則のいく぀かを持っおいるので、今日のレビュヌは良い出発点になるず思いたす。






pytestの有無にかかわらずTDD。無料のりェビナヌ






続きを読む






All Articles