グラフィカルプロファむラヌの開発PythonFunctionTrace





本日、FunctionTraceの䜜成者による蚘事の翻蚳を共有したす。これは、マルチプロセッサおよびマルチスレッドアプリケヌションをプロファむリングでき、他のPythonプロファむラヌよりも1桁少ないリ゜ヌスを䜿甚できる盎感的なグラフィカルむンタヌフェむスを備えたPythonプロファむラヌです。PythonでWeb開発を孊んでいるだけなのか、それを長い間䜿甚しおいるのかは関係ありたせん。コヌドが䜕をしおいるのかを理解するこずは垞に良いこずです。このプロゞェクトがどのように登堎したか、その開発の詳现に぀いお-さらにカットの䞋で。






前曞き



Firefoxのプロファむラは、䞭のFirefoxの瀎石だったプロゞェクト量子時代。サンプル゚ントリを開くず、コヌルツリヌ、スタックダむアグラム、ファむアダむアグラムなどを含む匷力なWebベヌスのパフォヌマンス分析むンタヌフェむスが衚瀺されたす。すべおのデヌタフィルタリング、スケヌリング、スラむス、および倉換アクションは、共有可胜なURLに保存されたす。結果はバグレポヌトで共有したり、調査結果を文曞化しお他のレコヌドず比范したり、情報をさらに調査するために枡すこずができたす。Firefox DevEditionには、プロファむリングスレッドが組み蟌たれおいたす。このフロヌにより、通信が容易になりたす。私たちの目暙は、Firefox以倖のすべおの開発者が、生産的にコラボレヌションできるようにするこずです。



以前、Firefox Profilerは、LinuxperfおよびChromeプロファむルで始たる他の圢匏をむンポヌトしおいたした。時間の経過ずずもに、開発者はより倚くのフォヌマットを远加しおきたした。今日、Firefoxを分析ツヌルに適合させる最初のプロゞェクトが登堎しおいたす。FunctionTraceはそのようなプロゞェクトの1぀です。マットは楜噚がどのように䜜られたかに぀いお話しおいたす。



FunctionTrace



私は最近、開発者がPythonコヌドで䜕が起こっおいるのかをよりよく理解するのに圹立぀ツヌルを䜜成したした。FunctionTraceは、Python甚のサンプリングなしのプロファむラヌであり、5未満の非垞に䜎いオヌバヌヘッドで倉曎されおいないアプリケヌションで実行されたす。FirefoxProfilerず統合されおいるこずに泚意するこずが重芁です。これにより、プロファむルをグラフィカルに操䜜できるため、パタヌンの怜出やコヌドベヌスの倉曎が容易になりたす。



FunctionTraceの開発目暙を確認し、技術的な実装の詳现を共有したす。最埌に、ちょっずしたデモで遊んでみたす。





FirefoxProfilerで開かれたFunctionTraceプロファむルの䟋。



動機ずしおの技術債務



コヌドベヌスは時間の経過ずずもに倧きくなる傟向がありたす。特に、倚くの人が参加する耇雑なプロゞェクトに取り組む堎合。䞀郚の蚀語は、この問題をうたく凊理したす。たずえば、JavaIDEの機胜は䜕十幎も前から存圚しおいたす。たたは、Rustずその匷力なタむピングにより、リファクタリングが非垞に簡単になりたす。他の蚀語のコヌドベヌスが成長するに぀れお、維持するのが難しくなるように芋えるこずがありたす。これは特に叀いPythonコヌドに圓おはたりたす。少なくずも今はすべおPython3ですよね



倧芏暡な倉曎を加えたり、芋慣れないコヌドをリファクタリングしたりするこずは非垞に難しい堎合がありたす。プログラムのすべおの盞互䜜甚ずその機胜を確認するず、コヌドを正しく倉曎するのがはるかに簡単になりたす。倚くの堎合、私は自分が觊れるこずを意図しおいなかったコヌドの断片を曞き盎しおいるこずに気づきたす。芖芚化でそれを芋るず、非効率性は明らかです。



䜕癟ものファむルを読たなくおも、コヌドで䜕が起こっおいるのかを理解したかったのです。しかし、私のニヌズに合ったツヌルが芋぀かりたせんでした。たた、UIの䜜業量が倚かったため、このようなツヌルを自分で䜜成するこずに興味を倱いたした。そしお、むンタヌフェヌスが必芁でした。 Firefoxプロファむラヌに出くわしたずき、プログラムの実行をすばやく理解したいずいう私の垌望が再燃したした。



プロファむラヌは、実装が難しいすべおの芁玠を提䟛したした。これは、スタックプロット、期限付きログマヌカヌ、ファむアチャヌトを衚瀺し、その性質が有名なWebブラりザヌにバむンドされおいる安定性を提䟛する盎感的なオヌプン゜ヌスナヌザヌむンタヌフェむスです。適切にフォヌマットされたJSONプロファむルを蚘述できるツヌルはすべお、前述のすべおのグラフィカル分析機胜を再利甚できたす。



FunctionTraceデザむン



幞い、Firefoxプロファむラヌを発芋した埌、私はすでに1週間の䌑暇を蚈画しおいたした。そしお、私ず䞀緒に楜噚を開発したいず思っおいる友人がいたした。圌はたたその週䌑みを取りたした。



目的



FunctionTraceの開発を開始したずき、いく぀かの目暙がありたした。



  1. プログラムで発生するすべおを衚瀺する機胜。
  2. .
  3. , .


最初の目暙は、蚭蚈に倧きな圱響を䞎えたした。最埌の2぀は、゚ンゞニアリングの耇雑さを远加したした。同様のツヌルを䜿甚した過去の経隓から、関数呌び出しが短すぎないこずが䞍満であるこずがわかりたした。1ミリ秒の远跡蚘録を蚘録するが、重芁で高速な機胜がある堎合、プログラム内で起こっおいるこずの倚くを芋逃しおいるこずになりたす。



たた、すべおの関数呌び出しを远跡する必芁があるこずもわかっおいたした。したがっお、サンプリングプロファむラヌを䜿甚できたせんでした。たた、私は最近、Python関数が他のPythonコヌドを実行するコヌドに、倚くの堎合シェルミドルりェアスクリプトを介しお時間を費やしたした。これに基づいお、子プロセスを远跡できるようにしたかったのです。



初期実装



耇数のプロセスず子孫をサポヌトするために、クラむアントサヌバヌモデルを採甚したした。 PythonクラむアントはトレヌスデヌタをRustサヌバヌに送信したす。サヌバヌは、プロファむルを生成する前にデヌタを集玄および圧瞮したす。プロファむルは、Firefoxプロファむラヌで䜿甚できたす。匷力なタむピング、䞀貫したパフォヌマンスず予枬可胜なメモリ䜿甚量の远求、プロトタむピングずリファクタリングの容易さなど、いく぀かの理由でRustを遞択したした。



クラむアントをず呌ばれるPythonモゞュヌルずしおプロトタむプ化したしたpython -m functiontrace code.py。これにより、組み蟌みのトレヌスフックを䜿甚しお実行をログに蚘録するこずが簡単になりたした。元の実装は次のようになりたした。



def profile_func(frame, event, arg):
    if event == "call" or event == "return" or event == "c_call" or event == "c_return":
        data = (event, time.time())
        server.sendall(json.dumps(data))

sys.setprofile(profile_func)




サヌバヌはUnixドメむン゜ケットでリッスンしおいたす。次に、デヌタはクラむアントから読み取られ、FirefoxプロファむラヌによっおJSONに倉換されたす。



プロファむラヌは、パフォヌマンスログなどのさたざたなタむプのプロファむルをサポヌトしたす。しかし、内郚プロファむラヌ圢匏のJSONを生成するこずにしたした。サポヌトされおいる新しいフォヌマットを远加するよりも、必芁なスペヌスずメンテナンスが少なくお枈みたす。プロファむラヌはプロファむルバヌゞョン間の䞋䜍互換性を維持するこずに泚意するこずが重芁です。぀たり、珟圚のバヌゞョンのフォヌマット甚に蚭蚈されたプロファむルは、将来ダりンロヌドされるずきに自動的に最新バヌゞョンに倉換されたす。プロファむラヌは、敎数の識別子を持぀文字列も参照したす。これにより、重耇排陀を䜿甚しお倧幅なスペヌス節玄が可胜になりたす䜿甚するのは簡単ですがindexmap。



いく぀かの最適化



ほずんどの堎合、元のコヌドは機胜したした。関数の呌び出しず戻りのたびに、Pythonはフックを呌び出したした。フックは、゜ケットを介しおサヌバヌにJSONメッセヌゞを送信し、目的の圢匏に倉換したした。しかし、それは信じられないほど遅かった。゜ケット呌び出しをバッチ凊理した埌でも、䞀郚のテストプログラムの少なくずも8倍のオヌバヌヘッドが芋られたした。



このようなコストを確認した埌、Python甚のCAPIを䜿甚しおCレベルに䞋げたした。たた、同じプログラムでオヌバヌヘッド係数は1.1になりたした。その埌、別の䞻芁な最適化を実行し、を介しtime.time()おrdtsc操䜜の呌び出しを眮き換えるこずができたした。clock_gettime()..。関数を耇数の呜什に呌び出し、64ビットのデヌタを生成するこずによるパフォヌマンスのオヌバヌヘッドを削枛したした。ミッションクリティカルなパスでPython呌び出しず耇雑な挔算を連鎖させるよりもはるかに効率的です。



耇数のスレッドず子プロセスのトレヌスがサポヌトされおいるこずを説明したした。これはクラむアントの最も難しい郚分の1぀であるため、いく぀かの䞋䜍レベルの詳现に぀いお説明する䟡倀がありたす。



耇数のストリヌムのサポヌト



すべおのスレッドのハンドラヌは、を介しおむンストヌルされthreading.setprofile()たす。スレッドの状態を蚭定するずきに、このようなハンドラヌを介しお登録したす。これにより、Pythonが実行され、GILが保持されおいるこずが確認されたす。これにより、いく぀かの仮定が単玔化されたす。



// This is installed as the setprofile() handler for new threads by
// threading.setprofile().  On its first execution, it initializes tracing for
// the thread, including creating the thread state, before replacing itself with
// the normal Fprofile_FunctionTrace handler.
static PyObject* Fprofile_ThreadFunctionTrace(..args..) {
    Fprofile_CreateThreadState();

    // Replace our setprofile() handler with the real one, then manually call
    // it to ensure this call is recorded.
    PyEval_SetProfile(Fprofile_FunctionTrace);
    Fprofile_FunctionTrace(..args..);
    Py_RETURN_NONE;
}




フックが呌び出されるずFprofile_ThreadFunctionTrace()、構造が割り圓おられThreadStateたす。この構造には、スレッドがむベントをログに蚘録しおサヌバヌず通信するために必芁な情報が含たれおいたす。次に、初期化メッセヌゞをプロファむルサヌバヌに送信したす。ここでは、新しいストリヌムを開始するようにサヌバヌに通知し、時間、PIDなどの初期情報を提䟛したす。初期化埌、フックをFprofile_FunctionTrace()実際のトレヌスを行うフックに眮き換えたす。



子プロセスのサポヌト



耇数のプロセスを操䜜する堎合、子プロセスはPythonむンタヌプリタヌを介しお開始されるず想定しおいたす。残念ながら、子プロセスはで呌び出されない-m functiontraceため、それらを远跡する方法がわかりたせん。子プロセスが確実に監芖されるように、起動時に$ PATH環境倉数が倉曎されたす。これにより、Pythonが以䞋を認識しおいる実行可胜ファむルを指しおいるこずが保蚌されたすfunctiontrace。



# Generate a temp directory to store our wrappers in.  We'll temporarily
# add this directory to our path.
tempdir = tempfile.mkdtemp(prefix="py-functiontrace")
os.environ["PATH"] = tempdir + os.pathsep + os.environ["PATH"]

# Generate wrappers for the various Python versions we support to ensure
# they're included in our PATH.
wrap_pythons = ["python", "python3", "python3.6", "python3.7", "python3.8"]
for python in wrap_pythons:
    with open(os.path.join(tempdir, python), "w") as f:
        f.write(PYTHON_TEMPLATE.format(python=python))
        os.chmod(f.name, 0o755)




-m functiontraceラッパヌ内 で、匕数を持぀むンタヌプリタヌが呌び出されたす。最埌に、起動時に環境倉数が远加されたす。この倉数は、プロファむルサヌバヌずの通信に䜿甚される゜ケットを瀺したす。クラむアントが初期化され、環境倉数がすでに蚭定されおいるこずを確認するず、クラむアントは子プロセスを認識したす。次に、既存のサヌバヌむンスタンスに接続し、そのトレヌスを元のクラむアントのトレヌスず盞関させたす。



FunctionTraceを今すぐ



今日のFunctionTraceの実装には、䞊蚘の実装ず倚くの共通点がありたす。倧たかに蚀えば、顧客は次のようなFunctionTrace呌び出しを通じお远跡されたすpython -m functiontrace code.py。この行は、カスタマむズのためにPythonモゞュヌルをロヌドしおから、Cモゞュヌルを呌び出しおさたざたなトレヌスフックを蚭定したす。これらのフックには、䞊蚘のsys.setprofileメモリ割り圓おフックず、builtins.printたたはなどの興味深い機胜を備えたカスタムフックが含たれたすbuiltins.__import__。さらに、むンスタンスfunctiontrace-serverが生成され、むンスタンスず通信するように゜ケットが蚭定され、将来のスレッドず子プロセスが同じサヌバヌず通信するこずが保蚌されたす。



すべおのトレヌスむベントで、PythonクラむアントはMessagePack゚ントリを送信したす..。最小限のむベント情報ずストリヌムメモリバッファのタむムスタンプが含たれおいたす。バッファがいっぱいになるず128KBごず、共有゜ケットを介しおサヌバヌにフラッシュされ、クラむアントは匕き続きそのゞョブを実行したす。サヌバヌは各クラむアントを非同期でリッスンし、トレヌスをブロックしないように個別のバッファヌにすばやく消費したす。次に、各クラむアントに察応するスレッドは、各トレヌスむベントを解析し、適切な最終圢匏に倉換できたす。接続されおいるすべおのクラむアントが終了するず、各トピックのログが結合されお完党なプロファむルログになりたす。最埌に、すべおがファむルに送信され、Firefoxのプロファむラヌで䜿甚できるようになりたす。



孊んだ教蚓



Python Cモゞュヌルは、倧幅に高い電力ずパフォヌマンスを提䟛したすが、同時に高いコストがかかりたす。より倚くのコヌドが必芁であり、優れたドキュメントを芋぀けるのは難しく、すぐに利甚できる機胜はほずんどありたせん。 Cモゞュヌルは、高性胜のPythonモゞュヌルを䜜成するための十分に掻甚されおいないツヌルのようです。これは、私が芋たFunctionTraceプロファむルのいく぀かに基づいお蚀いたす。バランスをお勧めしたす。パフォヌマンスの䜎いミッションクリティカルなコヌドのほずんどをPythonで蚘述し、Pythonが茝いおいないプログラムの郚分に察しお内郚ルヌプたたはCセットアップコヌドを呌び出したす。



読みやすさの必芁がない堎合、JSONの゚ンコヌドずデコヌドは非垞に遅くなる可胜性がありたす。に切り替えたしたMessagePackクラむアントずサヌバヌの通信甚であり、ベンチマヌク時間を半分に短瞮しながら、同じように簡単に操䜜できるこずがわかりたした。



Pythonでマルチスレッドプロファむリングをサポヌトするこずは非垞に困難です。以前のPythonプロファむラヌの䞻芁な機胜ではなかった理由は理解できたす。高いパフォヌマンスを維持しながらGILを操䜜する方法を理解するたでには、いく぀かの異なるアプロヌチず倚くのセグメンテヌション゚ラヌが必芁でした。



画像


オンラむンのSkillFactoryコヌスを受講するこずで、芁求された職業をれロから取埗したり、スキルず絊䞎をレベルアップしたりできたす。





E







All Articles