火焰圖(flame graph)
剖析器可以收集數萬筆呼叫堆疊樣本,但一張巨大的堆疊表格根本看不懂。火焰圖是一張讓那堆堆疊瞬間可讀的圖:它是「看見」程式時間去向最受歡迎的方式。
這樣讀它。每一筆取樣到的呼叫堆疊是一個垂直的欄:main 在最底、它呼叫的函式在上面,依此往上直到取樣那一刻正在執行的東西。火焰圖把所有這些欄並排合併並排序,所以任何方塊的寬度就是該函式(連同它呼叫的一切)出現在堆疊上的樣本比例——越寬代表越多時間。y 軸是呼叫深度,不是時間;x 軸也不是時間順序,只是按字母排序,好讓相同的堆疊堆成一個寬方塊。所以你沿著頂緣掃視:頂端任何寬廣的平台都是一個自己在執行(而不只是在等被呼叫者)很多樣本的函式——那就是你的熱點。兩個重要變體:差分火焰圖(differential)依方塊在兩次剖析間長大或縮小的程度上色(紅表變寬、藍表變窄),這就是你看出某個改動讓什麼變快或變慢的方式;離 CPU 火焰圖(off-CPU)則是用「阻塞」時間而非執行時間的樣本建成,所以它顯示程式在哪裡等待(等鎖、等 I/O),而不是在哪裡計算。
它之所以重要,是因為它把剖析結果變成你幾秒就能讀懂、還能指給隊友看的東西。誠實的提醒:寬度是「樣本」比例而非精確時間,所以它繼承了取樣的統計本質;遞迴會讓一個函式看起來疊在自己身上;而且圖只顯示有取樣到的東西,所以一張 on-CPU 火焰圖在某個慢階段看起來空空的,通常代表時間花在離 CPU、被阻塞的地方,那時你需要的是離 CPU 變體。
$ perf record -F 999 -g ./myprog $ perf script | stackcollapse-perf.pl | flamegraph.pl > out.svg # 頂緣上寬廣的平台 = parse_line();那就是 CPU 實際待著的地方
標準流程:記錄堆疊、合併相同的、繪成 SVG,方塊寬度即樣本比例。
把寬度讀成「含有這個框的堆疊比例」,而非「花在這個函式內部的時間」——底下一個寬方塊之所以寬,可能只是因為它上面有個昂貴的被呼叫者;函式自身的成本是「沒被子框蓋住」的那部分。