Ruby 和 Python 分析器是如何工作的?
原文出處: Julia Evans???譯文出處:開源中國
你好! 我作為一名編寫Ruby?profiler的先驅,我想對現有的Ruby和Python?profiler如何工作進行一次調查。 這也有助于回答很多人的問題:“你怎么寫一個profiler?”
在這篇文章中,我們只關注CPUprofiler(而不是內存/堆profiler)。 我將解釋一些編寫profiler的一般基本方法,給出一些代碼示例,以及大量流行的Ruby和Pythonprofiler的例子,并告訴你它們是如何工作的。
在這篇文章中可能會有一些錯誤(為了研究這篇文章,我閱讀了14個不同的分析庫的代碼部分),請讓我們開始吧!
2種不同的profilers
有兩種基本CPU?profilers類型 –?sampling?profilers和tracing?profilers。
tracingprofilers記錄您的程序所調用的每個函數,然后在最后打印出報告。 samplingprofilers采用更加統計化的方法 – 他們每隔幾毫秒記錄程序的堆棧情況,然后報告結果。
使用sampling?profilers而不是tracing?profilers的主要原因是sampling?profilers的開銷較低。 如果每秒只抽取20或200個樣本,那不會花費多少時間。 而且它們非常有效率 – 如果您遇到嚴重的性能問題(比如80%的時間花費在1個慢速函數上),那么每秒200個樣本通常就足以確定那個函數的問題所在了!
分析器
下邊類出了我們這篇文章要討論的分析器(來源)。我之后將會解釋表格中的術語(setitimer,?rb_add_event_hook,?ptrace)。這里最有趣的是,所有的分析器都是通過一小部分函數的特性實現的。
python分析器
Name
Kind
How it works
cProfile | Tracing | PyEval_SetProfile |
line_profiler | Tracing | PyEval_SetTrace |
pyflame?(blog post) | Sampling | ptrace + custom timing |
stacksampler | Sampling | setitimer |
statprof | Sampling | setitimer |
vmprof | Sampling | setitimer |
pyinstrument | Sampling | PyEval_SetProfile |
gprof?(greenlet) | Tracing | greenlet.settrace |
python-flamegraph | Sampling | profiling thread + custom timing |
gdb hacks | Sampling | ptrace |
“gbd hacks”并不完全是一個Python分析器:它是一個講述如何實現用腳本包裝gdb來實現hacky分析器的鏈接。由于新版本的gdb事實上會展開Python堆棧,所以也是和Python有關的。一種簡化版的pyflame。
Ruby分析器
Name
Kind
How it works
stackprof?by tmm1 | Sampling | setitimer |
perftools.rb?by tmm1 | Sampling | setitimer |
rblineprof?by tmm1 | Tracing | rb_add_event_hook |
ruby-prof | Tracing | rb_add_event_hook |
flamegraph | Sampling | stackprof gem |
這些分析器中幾乎所有的都存在你的進程里面。
在我們開始詳細分析這些分析器之前,有一個非常重要的事情需要說明一下:除fyflame外所有的分析器都運行在你的Python/Ruby進程里面。如果你在一個Python/Ruby程序里面,你通常可以很容易的獲取該程序的堆棧。例如下邊代碼中的簡單的Python程序答應出每一個運行線程的堆棧:
1 2 3 4 5 6 7 8 9 10 11 12 | 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() |
你可以從下邊的輸出里面看到堆棧的函數名,行號,文件名等你在做分析的時候需要的所有信息。
1 2 3 | (‘test2.py’,?12,?‘<module>’,?‘bar()’) (‘test2.py’,?5,?‘bar’,?‘foo()’) (‘test2.py’,?9,?‘foo’,?‘for?line?in?traceback.extract_stack(frame):’) |
在Ruby程序中,獲取堆棧也很容易:你只需要通過caoller來獲取堆棧。
這些分析器處于性能考慮都是C擴展所有它們有一點不一樣,但是Ruby/Python程序的C擴展也可以很容易的獲取調用堆棧。
追蹤分析器是如何工作的
我調查過上邊表格中所有的追蹤分析器:rblineprof、ruby-prof和cProfile。它們工作原理基本相同。它們都記錄所有的函數調用并且用C語言編寫來降低耗時。
它們是如何工作的呢?Ruby和Python都允許指定一個回調函數,當各種解釋事件(例如調用一個函數或者執行一行代碼)發生的時候調用。當回調函數被調用的時候,會記錄堆棧供以后分析。
我認為確切了解在代碼中哪里設置這些回調函數是很有用的,所以我連接了所有在github上邊的相關代碼。
在Python中,可以通過PyEval_SetTrace或者?PyEval_SetProfile設置回調函數。在Python官方文檔的分析和追蹤里有說明。文檔中說道:除了追蹤函數會收到line-number事件外“PyEval_SetTrace和PyEval_SetProfile一樣。
代碼:
line_profiler 使用PyEval_SetTrace設置回調:看line_profiler.pyx的157行
cProfiles 使用PyEval_SetProfile設置回調:看_lsprof.c的693行(cProfile是用Isprof實現的)
在Ruby里,你可以用rb_add_event_hook來設置回調,我找不到任何關于此處是如何調用的文檔
1 2 3 4 | 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的類型是
1 2 | static?void prof_event_hook(rb_event_flag_t?event,?VALUE?data,?VALUE?self,?ID?mid,?VALUE?klass) |
這看起來像極了Python的PyEval_SetTrace,但是比Python更靈活——您可以選擇你關注的事件類型(就像“函數調用”一樣)。
代碼:
ruby-prof 調用rb_add_event在:ruby-prof.c line 329
rblineprof調用rb_add_event_hook在:rblineprof.c line 649
追蹤分析器的缺點
追蹤分析器的主要的缺點是它的實現方式是對于每個函數/行代碼都執行固定的次數,這樣可能使你做出錯誤的決定。例如,如果你有某個事物的兩個實現:一個通過大量的函數調用實現,另一個沒有大量函數調用,兩個實現耗時相同,有大量函數調用的相比沒有大量函數調用的在分析的時候會變得慢。
為了測試這一點,我做了一個包含下邊內容的小文件test.py,并且比較了python -mcProfile test.py和python test.py的耗時。python test.py執行需要大約0.6秒,python -mcProfile test.py執行需要大約1秒。對于這個特定的例子cProfile引入了額外的大約60%的開銷。
1 2 3 4 5 6 7 | def?recur(n): ????if?n?==?0: return recur(n–1) for?i?in?range(5000): recur(700) |
cProfile文檔中說:
Python的解釋語言的特性往往會增加執行的開銷,對于典型的應用確定性分析僅僅會增加很少運行開銷。
這似乎是一個合理的說法:上邊的示例(執行350萬次函數調用)顯然不是個典型的Python程序,并且幾乎任何其他程序開銷都比該示例小。
我沒有測試ruby-prof(一個ruby追蹤分析器)的開銷,但是它的README說:
大多數程序開分析器耗時將會是原來的兩倍,并且高度遞歸程序(斐波那契數列)耗時將會是原來的三倍。
采樣分析器都怎么工作的:setitimer
現在討論第二種分析器:采樣分析器。
大多數Ruby和Python的采樣分析器都是通過系統調用setitimer實現的。這是怎么回事呢?
好吧,比方說你想要每秒獲取一個程序的堆棧50次,一種方法是:
請求Linux內核每20毫秒給你發送一個信號(使用系統調用setitimer)
注冊一個信號處理器在每次獲得信號的時候記錄堆棧。
當結束分析的時候,請求Linux停止發送信號并且打印輸出。
如果你想要看一個實際的用setitimer實現采樣分析器的例子的話,我認為stacksampler.py是一個最好的例子,stacksampler.py是一個有用的有效的分析器并且代碼只有大約100行,好酷啊!
stacksampler.py只有100多行的一個原因是:當你把一個Python函數注冊成信號處理器的時候,該函數被傳送到你的Python程序的當前堆棧中。所以stacksampler.py信號處理器注冊是非常簡單的:
1 2 3 4 5 6 7 8 | 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的分析器并找到它們調用settimer的代碼:
stackprof?(Ruby): in?stackprof.c line 118
perftools.rb?(Ruby): in?this patch which seems to be applied when the gem is compiled (?)
stacksampler?(Python):?stacksampler.py line 51
statprof?(Python):?statprof.py line 239
vmprof?(Python):?vmprof_unix.c line 294
關于setitimer很重要的一點是,你需要決定如何計算時間。你想要真正的20 ms的“掛鐘”時間?你想要20 ms的用戶CPU時間?或者20 ms的用戶+系統CPU時間?如果你仔細看電話網站上的內容,你就會發現,這些分析器實際上對setitimer做出了不同的選擇 — 有時候它是可配置的,有時候卻不可。setitimer手冊頁十分精悍,并且值得去讀懂上面所有的觀點。
@mgedmin?在推特上指出了一個使用setitimer時出現的有趣的問題,這個問題和這個問題擁有的一系列更多細節。
一個有趣的基于setitimer分析器的問題就是定時器產生的信號!信號有時候能中斷系統調用!系統調用有時候需要幾毫秒!如果測試太平凡,你會讓你的程序永遠循環執行系統調用!
不使用setitimer的采樣分析器
有些采樣分析器不使用setitimer:
pyinstrument使用PyEval_SetProfile(所以它在某種程度上是跟蹤分析器),但是當它的跟蹤回調函數被調用時,它并不總是收集堆棧樣本。下面是選擇何時測試堆棧跟蹤的代碼。更多信息,請看這篇博客文章。?(真相:?setitimer帶你了解Python中的主線程)
pyflame簡要介紹了Python代碼在外部調用ptracesystem的過程。根本上來講,它只是一個抓取樣本,睡眠,重復的循環,這里是sleep調用。
python-flamegraph以類似的方式在你的Python操作中開啟一個新的線程并且抓取堆棧跟蹤,睡眠,和重復。這里是sleep調用。
所有這3個分析器使用掛鐘定時采樣。
pyflame 博客
有很多關于pyflame是如何工作的。我不打算在這里進行介紹,但是Evan Klitke寫了很多關于它的非常好的博客:
Pyflame:超級工程的Ptracing的Python分析器來介紹pyflame
Pyflame雙解析器模式關于如何同時支持Python2和Python3
意想不到的python ABI變動增加了Python3.6的支持
釋放多線程Python堆棧
Pyflame打包
在Python中一個關于ptrace+syscalls的有趣的問題
使用ptrace的樂趣和好處,ptrace(續)
還有很多在?https://eklitzke.org/。所有有趣的東西,我會更詳細地閱讀——也許ptrace是比實現一個Ruby分析器process_vm_readv更好的方法!(process_vm_readv開銷低,因為它不會阻斷進程,但它也可以給你一個不一致的快照,因為它不會阻斷進程:))
先講解到這里了!
在這篇文章中我沒有涉及很多重要的細節 – 比如我基本上說vmprof和stacksampler是一樣的(但實際上它們不是 – vmprof支持線性分析和用C語言編寫的Python函數分析,我相信這在分析器中引入了更多的復雜性)。 但一些基本原理是一樣的,所以我認為這項調查是一個很好的起點。
來源:數盟
精彩活動
福利 · 閱讀 | 免費申請讀大數據新書 第23期
推薦閱讀
2017年數據可視化的七大趨勢!?
全球100款大數據工具匯總(前50款)?
論大數據的十大局限
大數據時代的10個重大變革
大數據七大趨勢 第一個趨勢是物聯網
Q:?你在Python學習使用中都有哪些心得?
歡迎留言與大家分享
請把這篇文章分享給你的朋友
轉載 / 投稿請聯系:hzzy@hzbook.com
更多精彩文章,請在公眾號后臺點擊“歷史文章”查看
總結
以上是生活随笔為你收集整理的Ruby 和 Python 分析器是如何工作的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Simulink框图和S-函数
- 下一篇: USB数据传输