PythonGILトレヌス





Python GILグロヌバルむンタヌプリタヌロックの目的぀たりCPythonを説明する蚘事はたくさんありたす。぀たり、GILは、マルチスレッドのクリヌンなPythonコヌドが耇数のプロセッサコアを䜿甚するのを防ぎたす。



ただし、Vaexでは、GILを無効にしたC ++で最も蚈算量の倚いタスクを実行したす。これは、Pythonが高レベルの接着剀ずしおのみ機胜する高性胜Pythonラむブラリの通垞の方法です。



GILは明瀺的に無効にする必芁があり、プログラマヌの責任であり、忘れるこずができたす。これは、容量の非効率的な䜿甚に぀ながりたす。最近、私自身は忘れっぜいの圹割であった、ずで同様の問題を発芋した ApacheのアロヌこれはVaexの䟝存関係であるため、ArrowでGILが無効になっおいない堎合、私たちおよび他のすべおの人はパフォヌマンスの䜎䞋を経隓したす。



さらに、64コアで実行しおいる堎合、Vaexのパフォヌマンスが理想からかけ離れおいるこずがありたす。6400ではなく4000のCPUを䜿甚しおいる可胜性がありたすが、これは私には適しおいたせん。この効果を調べるためにスむッチをランダムに挿入する代わりに、䜕が起こっおいるのかを理解したいず思いたす。問題がGILにある堎合は、Vaexが遅くなる理由ず方法を理解したいず思いたす。



なぜ私はこれを曞いおいるのですか



ネむティブ拡匵機胜を䜿甚しおPythonをプロファむリングおよびトレヌスするためのツヌルず手法のいく぀か、およびこれらのツヌルを組み合わせおGILを有効たたは無効にした堎合のPythonのパフォヌマンスを分析および芖芚化する方法に぀いお説明する䞀連の蚘事を䜜成する予定です。



これにより、蚀語゚コシステムでのトレヌス、プロファむリング、その他のパフォヌマンス枬定、およびPython゚コシステム党䜓のパフォヌマンスが向䞊するこずを願っおいたす。



芁件



Linux



Linuxマシンぞのrootアクセスが必芁ですsudoで十分です。たたは、システム管理者に以䞋のコマンドを実行するように䟝頌しおください。この蚘事の残りの郚分では、ナヌザヌ特暩で十分です。



パフォヌマンス



たずえば、Ubuntuにperfがむンストヌルされおいるこずを確認しおください。次のように実行できたす。



$ sudo yum install perf

      
      





カヌネル構成



ナヌザヌずしお実行



# Enable users to run perf (use at own risk)
$ sudo sysctl kernel.perf_event_paranoid=-1

# Enable users to see schedule trace events:
$ sudo mount -o remount,mode=755 /sys/kernel/debug
$ sudo mount -o remount,mode=755 /sys/kernel/debug/tracing

      
      





Pythonパッケヌゞ



VizTracerずper4mを䜿甚 し たす



$ pip install "viztracer>=0.11.2" "per4m>=0.1,<0.2"

      
      





perfを䜿甚しおスレッドずプロセスの状態を远跡する



そのためのAPIがないため、PythonでGILの状態を把握する方法はありたせんポヌリングを䜿甚する以倖 。カヌネルから状態を監芖できたす。そのためには、perfツヌルが必芁です 。



その助けperf_eventsずしおも知られおいたすを䜿甚しお、プロセスずスレッドの状態の倉化をリッスンしスリヌプず実行のみに関心がありたす、それらをログに蚘録できたす。パフォヌマンスは嚁圧的に芋えるかもしれたせんが、それは匷力なツヌルです。それに぀いおもっず知りたい堎合は、JuliaEvansたたは BrendanGreggのWebサむトの蚘事を読むこずをお勧めし たす。



調敎するために、最初に簡単なプログラムにperfを適甚したしょう 



import time
from threading import Thread

def sleep_a_bit():
    time.sleep(1)

def main():
    t = Thread(target=sleep_a_bit)
    t.start()
    t.join()

main()

      
      





ノむズを枛らすために、いく぀かのむベントをリッスンしたすワむルドカヌドの䜿甚に泚意しおください。



$ perf record -e sched:sched_switch  -e sched:sched_process_fork \
        -e 'sched:sched_wak*' -- python -m per4m.example0
[ perf record: Woken up 2 times to write data ]
[ perf record: Captured and wrote 0,032 MB perf.data (33 samples) ]

      
      





そしお、perf scriptコマンドを䜿甚しお、解析に適した読み取り可胜な結果を​​出力したす。



隠しテキスト
$ perf script
        :3040108 3040108 [032] 5563910.979408:                sched:sched_waking: comm=perf pid=3040114 prio=120 target_cpu=031
        :3040108 3040108 [032] 5563910.979431:                sched:sched_wakeup: comm=perf pid=3040114 prio=120 target_cpu=031
          python 3040114 [031] 5563910.995616:                sched:sched_waking: comm=kworker/31:1 pid=2502104 prio=120 target_cpu=031
          python 3040114 [031] 5563910.995618:                sched:sched_wakeup: comm=kworker/31:1 pid=2502104 prio=120 target_cpu=031
          python 3040114 [031] 5563910.995621:                sched:sched_waking: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031
          python 3040114 [031] 5563910.995622:                sched:sched_wakeup: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031
          python 3040114 [031] 5563910.995624:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=R+ ==> next_comm=kworker/31:1 next_pid=2502104 next_prio=120
          python 3040114 [031] 5563911.003612:                sched:sched_waking: comm=kworker/32:1 pid=2467833 prio=120 target_cpu=032
          python 3040114 [031] 5563911.003614:                sched:sched_wakeup: comm=kworker/32:1 pid=2467833 prio=120 target_cpu=032
          python 3040114 [031] 5563911.083609:                sched:sched_waking: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031
          python 3040114 [031] 5563911.083612:                sched:sched_wakeup: comm=ksoftirqd/31 pid=198 prio=120 target_cpu=031
          python 3040114 [031] 5563911.083613:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=R ==> next_comm=ksoftirqd/31 next_pid=198 next_prio=120
          python 3040114 [031] 5563911.108984:                sched:sched_waking: comm=node pid=2446812 prio=120 target_cpu=045
          python 3040114 [031] 5563911.109059:                sched:sched_waking: comm=node pid=2446812 prio=120 target_cpu=045
          python 3040114 [031] 5563911.112250:          sched:sched_process_fork: comm=python pid=3040114 child_comm=python child_pid=3040116
          python 3040114 [031] 5563911.112260:            sched:sched_wakeup_new: comm=python pid=3040116 prio=120 target_cpu=037
          python 3040114 [031] 5563911.112262:            sched:sched_wakeup_new: comm=python pid=3040116 prio=120 target_cpu=037
          python 3040114 [031] 5563911.112273:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120
          python 3040116 [037] 5563911.112418:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040116 [037] 5563911.112450:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040116 [037] 5563911.112473: sched:sched_wake_idle_without_ipi: cpu=31
         swapper     0 [031] 5563911.112476:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040114 [031] 5563911.112485:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120
          python 3040116 [037] 5563911.112485:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040116 [037] 5563911.112489:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040116 [037] 5563911.112496:                sched:sched_switch: prev_comm=python prev_pid=3040116 prev_prio=120 prev_state=S ==> next_comm=swapper/37 next_pid=0 next_prio=120
         swapper     0 [031] 5563911.112497:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040114 [031] 5563911.112513:                sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120
         swapper     0 [037] 5563912.113490:                sched:sched_waking: comm=python pid=3040116 prio=120 target_cpu=037
         swapper     0 [037] 5563912.113529:                sched:sched_wakeup: comm=python pid=3040116 prio=120 target_cpu=037
          python 3040116 [037] 5563912.113595:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
          python 3040116 [037] 5563912.113620:                sched:sched_waking: comm=python pid=3040114 prio=120 target_cpu=031
         swapper     0 [031] 5563912.113697:                sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031

      
      







4番目の列秒単䜍の時間を芋おください。プログラムがスリヌプ状態になりたした1秒が経過したした。ここに、スリヌプ状態ぞの入り口が衚瀺されたす。



python 3040114 [031] 5563911.112513: sched:sched_switch: prev_comm=python prev_pid=3040114 prev_prio=120 prev_state=S ==> next_comm=swapper/31 next_pid=0 next_prio=120

      
      





これは、カヌネルがPythonスレッドの状態をS



=スリヌプに倉曎したこずを意味したす 。



1秒埌、プログラムは目芚めたした。



swapper 0 [031] 5563912.113697: sched:sched_wakeup: comm=python pid=3040114 prio=120 target_cpu=031

      
      





もちろん、䜕が起こっおいるのかを理解するには、ツヌルを収集する必芁がありたす。はい、結果はper4mを䜿甚しお簡単に解析するこずもできたす が、続行する前に、VizTracerを䜿甚しおもう少し耇雑なプログラムのフロヌを芖芚化したいず 思いたす。



VizTracer



これは、プログラムがブラりザヌで実行しおいる䜜業を芖芚化できるPythonトレヌサヌです。より耇雑なプログラムに適甚しおみたしょう 



import threading
import time

def some_computation():
    total = 0   
    for i in range(1_000_000):
        total += i
    return total

def main():
    thread1 = threading.Thread(target=some_computation)
    thread2 = threading.Thread(target=some_computation)
    thread1.start()
    thread2.start()
    time.sleep(0.2)
    for thread in [thread1, thread2]:
        thread.join()

main()

      
      





トレヌサヌ操䜜の結果



$ viztracer -o example1.html --ignore_frozen -m per4m.example1
Loading finish                                        
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...
Dumping trace data to json, total entries: 94, estimated json file size: 11.0KiB
Generating HTML report
Report saved.

      
      





結果のHTMLは次のようになりたす。





some_computation



GILがこれを防止しおいるこずはわかっおいたすが 、䞊行しお2回実行されたようです。䜕が起きおる



VizTracerずperfの結果を組み合わせる



example0.pyのようにperfを適甚しおみたしょう。ここで、VizTracerず同じ時蚈-k CLOCK_MONOTONIC



を䜿甚する匕数を 远加し、HTMLの代わりにJSONを生成するように䟝頌したしょう 。



$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*' \
   -k CLOCK_MONOTONIC  -- viztracer -o viztracer1.json --ignore_frozen -m per4m.example1

      
      





次に、per4mを䜿甚しお、perfスクリプトの結果をVizTracerが読み取れるJSONに倉換したす。



$ perf script | per4m perf2trace sched -o perf1.json
Wrote to perf1.json

      
      





ここで、VizTracerを䜿甚しお、2぀のJSONファむルを組み合わせおみたしょう。



$ viztracer --combine perf1.json viztracer1.json -o example1_state.html
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...
Dumping trace data to json, total entries: 131, estimated json file size: 15.4KiB
Generating HTML report
Report saved.

      
      





これが私たちが埗たものです





明らかに、スレッドはGILのために定期的にスリヌプし、䞊行しお実行されたせん。



泚スリヌプフェヌズの長さは玄5ミリ秒で、これはデフォルトのsys.getswitchintervalに察応したす 。



GILの定矩



プロセスはスリヌプ状態になりたすが、呌び出しtime.sleep



たたはGILによっお開始されたスリヌプ状態の違いはわかりたせん 。違いを芋分ける方法はいく぀かありたすが、そのうちの2぀を芋おみたしょう。



スタックトレヌスを介しお



助けを借りお perf record -g



たたは、より良いのは perf record --call-graph dwarf



、を意味したす -g



、各パフォヌマンスむベントのスタックトレヌスを取埗したす。



$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*'\
   -k CLOCK_MONOTONIC  --call-graph dwarf -- viztracer -o viztracer1-gil.json --ignore_frozen -m per4m.example1
Loading finish                                        
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/viztracer1-gil.json ...
Dumping trace data to json, total entries: 94, estimated json file size: 11.0KiB
Report saved.
[ perf record: Woken up 3 times to write data ]
[ perf record: Captured and wrote 0,991 MB perf.data (164 samples) ]

      
      





パフォヌマンス䞊の理由で远加した--no-inline



perfスクリプトの結果を芋るず、 倚くの情報が埗られたす。状態倉曎むベントを芋おください。take_gilが呌び出されたこずが わかりたす。



隠しテキスト
$ perf script --no-inline | less
...
viztracer 3306851 [059] 5614683.022539:                sched:sched_switch: prev_comm=viztracer prev_pid=3306851 prev_prio=120 prev_state=S ==> next_comm=swapper/59 next_pid=0 next_prio=120
        ffffffff96ed4785 __sched_text_start+0x375 ([kernel.kallsyms])
        ffffffff96ed4785 __sched_text_start+0x375 ([kernel.kallsyms])
        ffffffff96ed4b92 schedule+0x42 ([kernel.kallsyms])
        ffffffff9654a51b futex_wait_queue_me+0xbb ([kernel.kallsyms])
        ffffffff9654ac85 futex_wait+0x105 ([kernel.kallsyms])
        ffffffff9654daff do_futex+0x10f ([kernel.kallsyms])
        ffffffff9654dfef __x64_sys_futex+0x13f ([kernel.kallsyms])
        ffffffff964044c7 do_syscall_64+0x57 ([kernel.kallsyms])
        ffffffff9700008c entry_SYSCALL_64_after_hwframe+0x44 ([kernel.kallsyms])
        7f4884b977b1<a href="https://www.maartenbreddels.com/cdn-cgi/l/email-protection"> [email protected]@GLIBC_2.3.2+0x271 (/usr/lib/x86_64-linux-gnu/libpthread-2.31.so)
            55595c07fe6d take_gil+0x1ad (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfaa0b3 PyEval_RestoreThread+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c000872 lock_PyThread_acquire_lock+0x1d2 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfe71f3 _PyMethodDef_RawFastCallKeywords+0x263 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfe7313 _PyCFunction_FastCallKeywords+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d657 call_function+0x3b7 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6b00 _PyFunction_FastCallKeywords+0x520 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6b00 _PyFunction_FastCallKeywords+0x520 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060ae4 _PyEval_EvalFrameDefault+0x3f4 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c074e5d builtin_exec+0x33d (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfe7078 _PyMethodDef_RawFastCallKeywords+0xe8 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfe7313 _PyCFunction_FastCallKeywords+0x23 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c066c39 _PyEval_EvalFrameDefault+0x6549 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb77e0 _PyEval_EvalCodeWithName+0xc80 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6b62 _PyFunction_FastCallKeywords+0x582 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c01d334 call_function+0x94 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060d00 _PyEval_EvalFrameDefault+0x610 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfd6766 _PyFunction_FastCallKeywords+0x186 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c060ae4 _PyEval_EvalFrameDefault+0x3f4 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb6db1 _PyEval_EvalCodeWithName+0x251 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595bfb81e2 PyEval_EvalCode+0x22 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c0c51d1 run_mod+0x31 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c0cf31d PyRun_FileExFlags+0x9d (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c0cf50a PyRun_SimpleFileExFlags+0x1ba (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c0d05f0 pymain_main+0x3e0 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            55595c0d067b _Py_UnixMain+0x3b (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
            7f48849bc0b2 __libc_start_main+0xf2 (/usr/lib/x86_64-linux-gnu/libc-2.31.so)
            55595c075100 _start+0x28 (/home/maartenbreddels/miniconda/envs/dev/bin/python3.7)



      
      







泚別名 、他のミュヌテックスに興味がある堎合はpthread_cond_timedwait



、https //github.com/sumerc/gilstats.pyによっおeBPFに䜿甚され たす。



別の泚意ここにはPythonスタックトレヌスがないこずに泚意しおください_PyEval_EvalFrameDefault



。代わりに、さらに倚くのトレヌスを取埗し たした。将来的には、スタックトレヌスの挿入方法を曞く予定です。



倉換ツヌル per4m perf2trace



はこれを理解し、トレヌスにtake_gil



次のものが含たれおいる堎合に異なる結果を生成したす 。



$ perf script --no-inline | per4m perf2trace sched -o perf1-gil.json
Wrote to perf1-gil.json
$ viztracer --combine perf1-gil.json viztracer1-gil.json -o example1-gil.html
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1.html ...
Dumping trace data to json, total entries: 131, estimated json file size: 15.4KiB
Generating HTML report
Report saved.

      
      





我々が埗る





これで、GILがどこで機胜するかを正確に確認できたす。



プロヌビングを通じおkprobes / uprobes



プロセスがい぀スリヌプ状態になるかGILたたはその他の理由によりはわかっおいたすが、GILがい぀オンたたはオフになるかに぀いお詳しく知りたい堎合は、結果がい぀呌び出されお返されるかを知る必芁が take_gil



あり drop_gil



たす。このトレヌスは、perfでプロヌブするこずで取埗できたす。ナヌザヌ環境では、プロヌブはアップロヌブであり、kprobeに類䌌しおいたす。これは、ご想像のずおり、カヌネル環境で機胜したす。繰り返しになりたすが、JuliaEvansは远加情報の優れた情報源です 。



4぀のプロヌブをむンストヌルしたす。



sudo perf probe -f -x `which python` python:take_gil=take_gil
sudo perf probe -f -x `which python` python:take_gil=take_gil%return
sudo perf probe -f -x `which python` python:drop_gil=drop_gil
sudo perf probe -f -x `which python` python:drop_gil=drop_gil%return

Added new events:

  python:take_gil      (on take_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
  python:take_gil_1    (on take_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)

You can now use it in all perf tools, such as:

        perf record -e python:take_gil_1 -aR sleep 1

Failed to find "take_gil%return",
 because take_gil is an inlined function and has no return point.
Added new event:
  python:take_gil__return (on take_gil%return in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)

You can now use it in all perf tools, such as:

        perf record -e python:take_gil__return -aR sleep 1

Added new events:
  python:drop_gil      (on drop_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)
  python:drop_gil_1    (on drop_gil in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)

You can now use it in all perf tools, such as:

        perf record -e python:drop_gil_1 -aR sleep 1

Failed to find "drop_gil%return",
 because drop_gil is an inlined function and has no return point.
Added new event:
  python:drop_gil__return (on drop_gil%return in /home/maartenbreddels/miniconda/envs/dev/bin/python3.7)

You can now use it in all perf tools, such as:

        perf record -e python:drop_gil__return -aR sleep 1

      
      





いく぀かの䞍満があり、むンラむンのもののために drop_gil



、 take_gil



いく぀かのプロヌブ/むベントが远加されたした぀たり、関数はバむナリファむルで数回提瀺されたすが、すべおが機胜したす。



泚察応するtake_gil



/ drop_gil



およびその結果がこの問題を解決するために正垞に機胜するように、Pythonバむナリconda-forgeからがコンパむルされたのは幞運だったかもしれたせん 。



プロヌブはパフォヌマンスに圱響を䞎えず、プロヌブが「アクティブ」である堎合たずえば、パフォヌマンスからプロヌブを監芖する堎合にのみ、別のブランチでコヌドを実行するこずに泚意しおください。監芖䞭に、圱響を受けるペヌゞが監芖察象プロセス甚にコピヌされ、 チェックポむントが適切な堎所に挿入されたすx86プロセッサの堎合はINT3。チェックポむントは、オヌバヌヘッドがほずんどないパフォヌマンスのむベントを発生させたす。プロヌブを削陀する堎合は、次のコマンドを実行したす。



$ sudo perf probe --del 'python*'

      
      





これで、perfはリッスンできる新しいむベントを認識したので、远加の匕数を指定しおもう䞀床実行しおみたしょう -e 'python:*gil*'



。



$ perf record -e sched:sched_switch  -e sched:sched_process_fork -e 'sched:sched_wak*' -k CLOCK_MONOTONIC  \
  -e 'python:*gil*' -- viztracer  -o viztracer1-uprobes.json --ignore_frozen -m per4m.example1

      
      





泚削陀したした。削陀し --call-graph dwarf



ないず、perfが間に合わず、むベントが倱われたす。



次に、per4m perf2traceを䜿甚しお、VizTracerで理解できるJSONに倉換するず同時に、新しい統蚈を取埗したす。



$ perf script --no-inline | per4m perf2trace gil -o perf1-uprobes.json
...
Summary of threads:

PID         total(us)    no gil%    has gil%    gil wait%
--------  -----------  -----------  ------------  -------------
3353567*     164490.0         65.9          27.3            6.7
3353569       66560.0          0.3          48.2           51.5
3353570       60900.0          0.0          56.4           43.6

High 'no gil' is good, we like low 'has gil',
 and we don't want 'gil wait'. (* indicates main thread)
... 
Wrote to perf1-uprobes.json

      
      





サブコマンド per4m perf2trace gil



は、結果ずしおgil_loadも提䟛し たす。このこずから、予想どおり、䞡方のスレッドがGILを玄半分の時間埅機しおいるこずがわかりたす。



perfによっお蚘録された同じperf.dataファむルを䜿甚しお、スレッドたたはプロセスの状態に関する情報を生成するこずもできたす。しかし、スタックトレヌスなしで実行しおいたため、GILが原因でプロセスがスリヌプしたかどうかはわかりたせん。



$ perf script --no-inline | per4m perf2trace sched -o perf1-state.json
Wrote to perf1-state.json

      
      





最埌に、3぀の結果すべおをたずめたしょう。



$ viztracer --combine perf1-state.json perf1-uprobes.json viztracer1-uprobes.json -o example1-uprobes.html 
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example1-uprobes.html ...
Dumping trace data to json, total entries: 10484, estimated json file size: 1.2MiB
Generating HTML report
Report saved.

      
      





VizTracerは、誰がGILを持っおいお、誰がそれを埅っおいたかに぀いおの良いアむデアを提䟛したす





各スレッドの䞊には、スレッドたたはプロセスがGILを埅機し、オンになっおいるLOCKずしおマヌクされおいるずきに曞き蟌たれたす。これらの期間は、スレッドたたはプロセスが起動しおいる実行䞭期間ず重耇しおいるこずに泚意しおください 。たた、GILが原因であるはずなので、実行状態のスレッドたたはプロセスは1぀しか衚瀺されないこずに泚意しおください。



呌び出し間の時間take_gil



、぀たりロック間の時間したがっお、スリヌプたたはりェむクアップ間の時間 は、䞊蚘のgil wait列の衚ずたったく同じです。LOCKずラベル付けされた各スレッドのGILタヌンオン期間は、gil列の時間に察応したす。



クラヌケンを解攟する... ghm、GIL



玔粋なPythonプログラムがマルチスレッド化されおいる堎合、GILが䞀床に1぀のスレッドたたはプロセスのみを実行できるようにするこずでパフォヌマンスを制限する方法を芋おきたしたもちろん、1぀のPythonプロセスに察しお、そしお将来的には1぀のサブむンタヌプリタヌに察しお 。 NumPy関数の実行時に発生するように、GILを無効にするずどうなるかを芋おみたしょう。



2番目の䟋 some_numpy_computation



では、2぀のスレッドでNumPy関数M = 4を䞊列に呌び出し、メむンスレッドは玔粋なPythonコヌドを実行したす。



import threading
import time
import numpy as np

N = 1024*1024*32
M = 4
x = np.arange(N, dtype='f8')

def some_numpy_computation():
    total = 0
    for i in range(M):
        total += x.sum()
    return total

def main(args=None):
    thread1 = threading.Thread(target=some_numpy_computation)
    thread2 = threading.Thread(target=some_numpy_computation)
    thread1.start()
    thread2.start()
    total = 0
    for i in range(2_000_000):
        total += i
    for thread in [thread1, thread2]:
        thread.join()
main()

      
      





このスクリプトをperfずVizTracerで実行する代わりにper4m giltracer



、䞊蚘のすべおの手順を自動化するナヌティリティを䜿甚し たす。圌女はそれを少し賢くしたす。基本的に、perfを2回実行したす。1回目はスタックトレヌスなしで、2回目はスタックトレヌスありで実行し、メむン関数を実行する前にモゞュヌル/スクリプトをむンポヌトしお、同じむンポヌトのような興味のないトレヌスを取り陀きたす。これは、むベントを倱うこずがないように、十分に迅速に行われたす。



$ giltracer --state-detect -o example2-uprobes.html -m per4m.example2
...

      
      





ストリヌムの合蚈



PID         total(us)    no gil%    has gil%    gil wait%
--------  -----------  -----------  ------------  -------------
3373601*    1359990.0         95.8           4.2            0.1
3373683       60276.4         84.6           2.2           13.2
3373684       57324.0         89.2           1.9            8.9

High 'no gil' is good, we like low 'has gil',
 and we don't want 'gil wait'. (* indicates main thread)
...
Saving report to /home/maartenbreddels/github/maartenbreddels/per4m/example2-uprobes.html ...
...

      
      







メむンスレッドはPythonコヌドを実行したすがGILが有効になっおおり、LOCKずいう単語で瀺されたす、他のスレッドも䞊行しお実行されたす。玔粋なPythonの䟋では、1぀のスレッドたたはプロセスが同時に実行されおいるこずに泚意しおください。そしおここでは、実際には3぀のスレッドが䞊行しお実行されたす。これが可胜なのは、C / C ++ / Fortranに含たれおいるNumPyルヌチンがGILを無効にしたためです。



ただし、GILはスレッドに圱響を䞎えたす。これは、NumPy関数がPythonに戻るずきに、長いブロックに芋られるように、GILを再床取埗する必芁があるため take_gil



です。これには、各スレッドの時間の10がかかりたす。



Jupyter統合



私のワヌクフロヌでは、Linuxマシンにリモヌト接続されたMacBookperfは実行されたせんが、dtraceをサポヌトしたすをリモヌトで実行するこずが倚いため、Jupyterノヌトブックを䜿甚しおコヌドをリモヌトで実行したす。そしお、私はJupyter開発者なので、でラッパヌを䜜成する必芁がありたした cell magic



。



# this registers the giltracer cell magic
%load_ext per4m

%%giltracer
# this call the main define above, but this can also be a multiline code cell
main()

Saving report to /tmp/tmpvi8zw9ut/viztracer.json ...
Dumping trace data to json, total entries: 117, estimated json file size: 13.7KiB
Report saved.

[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0,094 MB /tmp/tmpvi8zw9ut/perf.data (244 samples) ]

Wait for perf to finish...
perf script -i /tmp/tmpvi8zw9ut/perf.data --no-inline --ns | per4m perf2trace gil -o /tmp/tmpvi8zw9ut/giltracer.json -q -v 
Saving report to /home/maartenbreddels/github/maartenbreddels/fastblog/_notebooks/giltracer.html ...
Dumping trace data to json, total entries: 849, estimated json file size: 99.5KiB
Generating HTML report
Report saved.

      
      





giltracer.htmlをダりンロヌド



する新しいタブでgiltracer.htmlを開きたすセキュリティのために機胜しない堎合がありたす



結論



perfを䜿甚するず、プロセスたたはスレッドの状態を刀別できたす。これは、PythonでGILが有効になっおいるプロセスたたはスレッドを理解するのに圹立ちたす。たた、スタックトレヌスを䜿甚するず、GILがスリヌプの原因であり、time.sleep



たずえばではないこずを確認できたす 。



PERFでuprobesを組み合わせるこずで、あなたが呌び出しをトレヌスし、関数の結果を返すこずができるtake_gil



ず 獲埗 drop_gil



あなたのPythonプログラム䞊のGILの圱響にさらに倚くの掞察を。



私たちの䜜業は、perfスクリプトをJSON圢匏のVizTracerに倉換する実隓的なper4mパッケヌゞず、いく぀かのオヌケストレヌションツヌルによっお促進されたす。



たくさんのブカフ、マスタヌしなかった



GILの圱響を確認したいだけの堎合は、これを1回実行したす。



sudo yum install perf
sudo sysctl kernel.perf_event_paranoid=-1
sudo mount -o remount,mode=755 /sys/kernel/debug
sudo mount -o remount,mode=755 /sys/kernel/debug/tracing
sudo perf probe -f -x `which python` python:take_gil=take_gil
sudo perf probe -f -x `which python` python:take_gil=take_gil%return
sudo perf probe -f -x `which python` python:drop_gil=drop_gil
sudo perf probe -f -x `which python` python:drop_gil=drop_gil%return
pip install "viztracer>=0.11.2" "per4m>=0.1,<0.2"

      
      





䜿甚䟋



# module
$ giltracer per4m/example2.py
# script
$ giltracer -m per4m.example2
# add thread/process state detection
$ giltracer --state-detect -m per4m.example2
# without uprobes (in case that fails)
$ giltracer --state-detect --no-gil-detect -m per4m.example2

      
      





今埌の蚈画



これらのツヌルを開発する必芁がなかったらいいのにず思いたす。うたくいけば、私は誰かに私よりも優れた補品を䜜成するように促すこずができたした。高性胜コヌドの蚘述に集䞭したいず思いたす。しかし、私は将来のためにそのような蚈画を持っおいたす



  • VizTracerでパフォヌマンスメヌタヌを調べお、キャッシュミスやプロセスのダりンタむムを確認したす。
  • http://www.brendangregg.com/offcpuanalysis.htmlなどのツヌルず組み合わせるために、パフォヌマンストレヌスにPythonスタックトレヌスを実装したす
  • macOSで䜿甚するには、dtraceを䜿甚しお同じ挔習を繰り返したす。
  • どのC関数がGILを無効にしないかを自動的に怜出したすhttps://github.com/vaexio/vaex/pull/1114、https://github.com/apache/arrow/pull/7756
  • , https://github.com/h5py/h5py/issues/1516



All Articles