先说Windows下CPU使用率这个概念:
CPU使用率:在任务管理器的刷新周期内CPU忙的时间与整个刷新周期的比值。默认的刷新周期是1s。
即1s内,反映出系统的CPU繁忙程度
我们打开Windows的任务管理器,可以看到CPU的使用率: 当然,这个CPU使用率是整个所有核心CPU的使用率。比如我本机是8核心的CPU。整体的CPU使用率 在某一瞬间是14%。
这个CPU使用率是如何计算出来的,有两个重要的时间sysTime和idleTime:
sysTime:表示该时间段内总的CPU时间=CPU处于用户态和内核态CPU时间的总和,即sysTime =kerneTimel + userTime
(注:这里并不包括idleTime,因为当CPU处于空闲状态时,是在内核模式下运行System Idle Process这个进程,所以kernelTime实际上已经包含了idleTime);
idleTime:表示在该时间段内CPU处于空闲状态的时间;
CPU% = 1 – idleTime / sysTime * 100说到这里,我们分析一个应用的高cpu问题,更多的是:分析用户态的CPU耗时。即,我们应用程序本身运行时消耗的CPU时间片总和。
然后,进入今天的正题,使用Windbg分析高CPU问题:
代码中有两个线程,一个是“正常”的计算输出线程NormalThread(每次输出,会休息一下 100s),一个是长时间运行的线程LongRunningThread,一直在计算,输出。
代码写好之后,设置为Any CPU架构,支持64位模式: 看程序输出: 很明显,3号线程是NormalThread, 4号线程是LongRunningThread。
从任务管理器上,能够发现,HighCpuDemo这个进程的CPU使用率是12%
这里有个问题:为什么要间隔一段时间抓2个dump?我们先把问题放在这。
Windbg打开这两个Dump后,分别执行以下命令:
0:000> .loadby sos clr 0:000> !runaway对比看着两个Dump的输出:
第一个Dump:
Debug session time: Sun Nov 25 20:16:39.000 2018 (GMT+8) System Uptime: 0 days 3:03:00.195 Process Uptime: 0 days 0:08:31.000 ............................. Loading unloaded module list . ntdll!ZwDeviceIoControlFile+0x14: 00007ffc`c01b03a4 c3 ret 0:000> .loadby sos clr 0:000> !runaway User Mode Time Thread Time 4:3758 0 days 0:07:38.531 3:325c 0 days 0:00:00.390 0:2248 0 days 0:00:00.015 6:4c3c 0 days 0:00:00.000 5:17d0 0 days 0:00:00.000 2:278 0 days 0:00:00.000 1:2424 0 days 0:00:00.000第二个Dump:
Debug session time: Sun Nov 25 20:17:06.000 2018 (GMT+8) System Uptime: 0 days 3:03:27.136 Process Uptime: 0 days 0:08:58.000 ............................. Loading unloaded module list . ntdll!ZwDeviceIoControlFile+0x14: 00007ffc`c01b03a4 c3 ret 0:000> .loadby sos clr 0:000> !runaway User Mode Time Thread Time 4:3758 0 days 0:08:01.984 3:325c 0 days 0:00:00.406 0:2248 0 days 0:00:00.015 6:4c3c 0 days 0:00:00.000 5:17d0 0 days 0:00:00.000 2:278 0 days 0:00:00.000 1:2424 0 days 0:00:00.000这里有个关键的Windbg指令 !runaway, 它有什么作用,输出的是什么:
This extension is a quick way to find out which threads are spinning out of control or consuming too much CPU time. The display identifies each thread by the debugger's internal thread numbering and by the thread ID in hexadecimal. The debugger IDs are also shown. Here is an example: 0:001> !runaway 7 User Mode Time Thread Time 0:55c 0:00:00.0093 1:1a4 0:00:00.0000 Kernel Mode Time Thread Time 0:55c 0:00:00.0140 1:1a4 0:00:00.0000 Elapsed Time Thread Time 0:55c 0:00:43.0533 1:1a4 0:00:25.0876使用这个指令,可以查看每个线程的"用户态"CPU使用时间:
从上面两个Dump中,我们能看出,4号线程 User Mode Time 一直在增加,我们看看4号线程的堆栈:
0:000> ~4s *** WARNING: Unable to verify checksum for System.Core.ni.dll System_Core_ni+0x588b44: 00007ffc`a4268b44 488b4de8 mov rcx,qword ptr [rbp-18h] ss:000000c1`df0ff2a8=000001d4633eb280 0:004> !clrstack OS Thread Id: 0x3758 (4) Child SP IP Call Site 000000c1df0ff280 00007ffca4268b44 System.Linq.Enumerable.Min(System.Collections.Generic.IEnumerable`1<Int32>) 000000c1df0ff2d0 00007ffc4b930660 *** WARNING: Unable to verify checksum for HighCpuDemo.exe HighCpuDemo.Program.LongRunningMethod() [c:\users\zhougq\documents\visual studio 2015\Projects\HighCpuDemo\Program.cs @ 54] 000000c1df0ff3a0 00007ffca7e24770 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 954] 000000c1df0ff470 00007ffca7e24604 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 902] 000000c1df0ff4a0 00007ffca7e245d2 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 891] 000000c1df0ff4f0 00007ffca7eacff2 System.Threading.ThreadHelper.ThreadStart() [f:\dd\ndp\clr\src\BCL\system\threading\thread.cs @ 111] 000000c1df0ff748 00007ffcaaf35863 [GCFrame: 000000c1df0ff748] 000000c1df0ffa98 00007ffcaaf35863 [DebuggerU2MCatchHandlerFrame: 000000c1df0ffa98]正如我们代码中所写的,LongRunningMethod方法一直在执行、消耗CPU资源。
定位到代码问题,就可以进一步修改代码,解决问题了。
以上就是使用Windbg 调试高CPU问题的方法思路,总结一下:
查看应用进程的CPU使用率间隔一段时间,抓两个Dump使用Windbg分析两个Dump文件,使用!runaway找到最消耗CPU时间片的线程,然后优化