準備開個系列來聊一下 PerfView 這款工具,熟悉我的朋友都知道我喜歡用 WinDbg,這東西雖然很牛,但也不是萬能的,也有一些場景他解決不了或者很難解決,這時候藉助一些其他的工具來輔助,是一個很不錯的主意。
很多朋友喜歡在專案中以記錄紀錄檔的方式來監控專案的流轉情況,其實 CoreCLR 也是這樣的,參考如下程式碼:
void gc_heap::fix_allocation_context (alloc_context* acontext, BOOL for_gc_p,
BOOL record_ac_p)
{
dprintf (3, ("Fixing allocation context %Ix: ptr: %Ix, limit: %Ix",
(size_t)acontext,
(size_t)acontext->alloc_ptr, (size_t)acontext->alloc_limit));
}
void gc_heap::background_sweep()
{
//concurrent_print_time_delta ("finished with mark and start with sweep");
concurrent_print_time_delta ("Sw");
dprintf (2, ("---- (GC%d)Background Sweep Phase ----", VolatileLoad(&settings.gc_index)));
//block concurrent allocation for large objects
dprintf (3, ("lh state: planning"));
}
void gc_heap::background_ephemeral_sweep()
{
dprintf (3, ("bgc ephemeral sweep"));
}
那這些紀錄檔會送到哪裡去呢,當然是 Windows 的 ETW 了,那有什麼工具可以方便提取呢? PerfView 就是這麼其中一款。
這一篇我們做一個 CPU 爆高的場景下如何尋找 熱點函數
的例子,看看如何用 PerfView 去挖。
很多場景下的 CPU 高,是因為某個或者某幾個執行緒在高頻的執行某個方法,有可能是死迴圈,有可能是陷入了CPU密集型方法內,解決這個問題一個好的思路就是對 CPU 進行取樣,比如我的 12 核電腦。
0:000> !cpuid
CP F/M/S Manufacturer MHz
0 6,5,2 2592
1 6,5,2 2592
2 6,5,2 2592
3 6,5,2 2592
4 6,5,2 2592
5 6,5,2 2592
6 6,5,2 2592
7 6,5,2 2592
8 6,5,2 2592
9 6,5,2 2592
10 6,5,2 2592
11 6,5,2 2592
取樣的原理就是週期性的去看下當前的 CPU 核中執行的幾個執行緒正在執行什麼方法, 當取樣到了幾萬個或者幾十萬個樣本之後,就可以對這些採集到的方法進行分組排序來找到 topN,那些 TopN 的方法自然就是導致 CPU 爆高可能的誘因。
windbg 有一個 !running
命令可以用來顯示當前處理器中正在執行的執行緒。
lkd> !running
System Processors: (0000000000000fff)
Idle Processors: (000000000000065e)
Prcbs Current (pri) Next (pri) Idle
0 fffff80268a33180 ffffaf8ec9bd8080 (15) fffff8026b526600 ................
5 ffffd900e1700180 ffffaf8eca36b080 ( 8) ffffd900e170b340 ................
7 ffffd900e1900180 ffffaf8ec2f18080 ( 8) ffffd900e190b340 ................
8 ffffd900e1a00180 ffffd900e1a0b340 ( 0) ffffd900e1a0b340 ................
11 ffffd900e1d00180 ffffaf8eb6bee080 ( 8) ffffd900e1d0b340 ................
接下來寫一個程式,讓其中一個執行緒無限迴圈,然後通過 PerfView 去找這個熱點。
internal class Program
{
static void Main(string[] args)
{
Task.Run(() => Test1()); //Test1 故意死迴圈
Task.Run(() => Test2()); //Test2 是一個正常函數
Console.WriteLine("我是主執行緒!");
Console.ReadLine();
}
static void Test1()
{
var i = 10;
var b = true;
while (i > 0)
{
b = !b;
}
}
static void Test2()
{
for (int i = 0; i < 10000; i++)
{
var j = string.Join(",", Enumerable.Range(0, 100));
}
Console.WriteLine("Test執行結束");
}
}
點選選單中的 Collect -> Collect
,彈出如下面板。
在這個面板中,選中如下幾項。
1)CPU Samples:
設定對 CPU 進行取樣。
2)CPU Sample Interval Msec
設定取樣的頻次是 1ms/次。
3)Max Collect Sec
設定總共取樣多少秒,這裡設定為 15 秒。
4).NET Symbol Collection
用來從微軟符號伺服器上拉取符號,和取樣無關哈。
上面都設定完畢後,就可以點選 Start Collection
採集了,不出意外的話,15s 之後你就會看到如下的截圖。
接下來點選 CPU Stacks
,在彈出的面板中選中我們的 程式
,雙擊之後就可以開啟如下面板。
從圖中可以看到,當前取樣了 15622
個樣本,符合 15 * 1000
,接下來把上面的 GroupPats
預設分組給清掉,截圖如下:
從圖中可以看到當前 Test1()
方法在 15622 個樣本中佔比 97.9%
,命中次數高達 15290 次,很明顯這是一個絕對的 熱點函數
,接下來就是翻原始碼為什麼 Test1 這麼高頻?
如果你想看雞肋的 火焰圖
,可以點選 Flame Graph
列表項。
好了,本篇就先聊這麼多吧。