取樣式剖析器與插樁式剖析器
/ perf: PURF /
要查出程式把時間花在哪裡,有兩種誠實的做法,它們就像查清楚一間忙碌辦公室整天在做什麼的兩種方式。第一種:你每隔一陣子走進去,拍張照記下誰在做什麼,拍了一千張之後就有了不錯的統計圖像——這就是取樣式剖析器(sampling profiler)。第二種:你發給每位員工一支碼錶,要他們記錄每件任務的進出時間——這就是插樁式剖析器(instrumentation profiler)。各自都告訴你某種真相,也各自以不同方式干擾了辦公室。
取樣式剖析器以固定頻率(譬如每秒一千次,常用硬體計時器或 PMU)中斷程式,並在每次中斷時記錄當前的呼叫堆疊——哪個函式正在執行、又是誰呼叫它的。它從不碰你的程式碼,只是從外部觀察。因為它只是週期性地偷看一眼,它的額外負擔很小且不太隨程式大小變化,但它看到的只是統計樣本,所以非常短或很少被打到的函式可能被低估,而且你要把結果讀成百分比而非精確次數。相對地,插樁式剖析器在每個函式的開頭與結尾插入額外的記錄程式碼(編譯器可以做到,例如 gcc -pg,工具也可以改寫二進位檔)。它能算出精確的呼叫次數並對每次呼叫計時,這很準——但插入的程式碼本身要花時間,對於極小的熱門函式,這個額外負擔可能蓋過並扭曲你正想量測的東西。
該用哪一種:先用取樣式剖析器,取得整支程式牆鐘時間去向的真實、低扭曲圖像,尤其在正式環境;當你需要精確呼叫次數、或想對某個有界區段計時時,用插樁式。插樁的經典陷阱是它改變了程式的時序(還可能讓內聯失效),所以一個實際上很便宜的函式,可能純粹因為量測負擔而看起來很貴——這正是整支程式分析時取樣為預設的原因。
取樣:計時器每秒觸發 1000 次 -> 記錄堆疊 -> {parse:612 次, sort:200, io:100} 約 61% 在 parse 插樁:每次呼叫記進出 -> {parse 被呼叫 120 萬次, 共 480 毫秒, sort 5 千次, 1 毫秒}
取樣給出時間去向的比例;插樁以額外負擔為代價,給出精確的呼叫次數與每函式總時間。
一個微妙處:純取樣式剖析器可能漏掉花在「阻塞」上的時間(等待 I/O 或鎖),因為計時器觸發時睡著的執行緒並未在執行——那種情況你需要的是離 CPU 剖析(off-CPU),而非 on-CPU 取樣。