VC驿站

 找回密码
 加入驿站

QQ登录

只需一步,快速开始

搜索
查看: 745|回复: 8

[原创] 调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

[复制链接]
70_avatar_middle
最佳答案
0 
在线会员 发表于 2019-12-22 13:37:26 | 显示全部楼层 |阅读模式
今天分享给大家一篇我写的排查高cpu占用率的文章。希望对你有启发。粘贴自我的博客。

如果大家喜欢,以后发新文章的时候,我会尽量同步发布到论坛一份。


前言如果我们自己的程序的CPU Usage(CPU占用率)飙升,并且居高不下,很有可能陷入了死循环。你知道怎么快速定位并解决吗?今天跟大家分享几种定位方法,希望对你有所帮助。
如何判断是否有死循环?
  • 通过电脑风扇的声音猜测。
    如果风扇一直响个不停,说明电脑很热。高CPU占用率会导致CPU发热量增大,从而导致风扇狂响。如果听到风扇响个不停,可以打开任务管理器看看CPU占用率是不是很高。如果发现是我们的进程导致的高CPU占用率,那么可以进一步查看是不是有死循环。
  • 通过CPU占用率来判断。
    对于多核CPU(尤其是性能强劲的CPU),一个核心的满负荷运转,并不会立刻导致CPU发热量明显增大,风扇可能不会有明显响动。这时根据风扇声音不能轻易判断出是否有死循环,但是我们可以通过CPU占用率来判断。
    如果CPU是单核的,那么当CPU处于满负荷运转状态,CPU占用率会接近100%。如果CPU是4核的,并且这4个核心都处于满负荷运转状态,那么CPU占用率会接近100%,如果只有一个核心是满负荷运转状态,那么CPU占用率会在25%(100 / 4 = 25)左右。如果我们发现某个进程的CPU占用率居高不下,有可能是死循环了。
  
注意: 很多死循环都是busy类型的,如果是idle类型的死循环,上面的方法不适用。


下面介绍几个我经常使用的工具,可以比较便捷的排查此类的问题。

1. process explorer

在前面的文章里跟大家介绍过,使用process explorer可以查看线程的调用栈及CPU占用率。如果程序里的某个功能迟迟不能完成,我的第一反应是,按Ctrl + Shift + Esc打开任务管理器(我已经使用process explorer替换了系统自带的任务管理器,所以启动的是process explorer。如何使用process explorer替换系统自带的任务管理器,请参考文章排错实战——使用process explorer替换任务管理器)。
启动process explorer后,双击我们关心的进程,切换到Thread页,在这里我们可以看到当前进程中的所有线程。双击某个线程就可以查看调用栈,在弹出的调用栈界面,点击左下角的Refresh按钮可以刷新。
如果每次刷新都能看到某个函数,很有可能是在这个函数中出现了死循环。对照源码,也许能直接能看出原因。
调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

注意: 需要正确加载调试符号才可以看到对应的函数名。

2. windbg

如果不能使用process explorer定位到具体的原因,可以使用windbg附加到进程中进行更深入的调查。我们需要找出哪个线程运行的时间最长,因为一般死循环的线程占用的CPU时间会比较长。应该怎么找呢?🤔
  • 使用.ttime命令
    .ttime可以查看当前线程的运行时间(用户态运行时间和内核态运行时间)。但是.ttime有个不足之处——没有输出相关的线程标识。我们需要根据其它信息来获取当前线程的标识。
    调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

如果想查看所有线程的运行时间怎么办呢?当然可以手动切换到另外一个线程,然后执行.ttime。如果线程数量很多的话,这可是个体力活。不要怕,我们可以通过命令~*e .ttime来获取每个线程的运行时间。因为.ttime输出结果中没有线程标识,我们需要执行命令 ~*e ? $tid;.ttime 把对应的线程ID一起输出。
  调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

简单向大家解释下这条命令:
  • ~*e会遍历所有线程并执行后面跟着的命令。其实,~*就可以遍历所有线程,比如我们在前面的文章里用到的~* kvn命令来查看所有线程的调用栈。但是对于某些命令,如果不加e,windbg可能不能正确解析,会报错。
  • ? $tid评估表达式$tid的值,?在windbg中表示Evaluate的意思,会评估后面表达式的值。$tid是伪变量,代表了当前线程的线程ID。
  • ; 分号是命令分割符。
  • .ttime查看当前线程的运行时间。
    整条命令的效果是:遍历每个线程,输出其对应的线程ID和运行时间。
  • 如果觉得上面的命令太长了,还可以使用更简单的命令!runaway查看线程运行时间。
    调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

下面是我用!runaway命令排查高CPU占用率的屏幕录像。
调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
3. visual studio

如果是正在开发的程序在运行过程中出现了死循环,我会考虑用vs来附加到进程(如果进程是通过Ctrl + F5启动的话,并没有被调试)。然后通过Parallel Stacks查看所有线程,并用肉眼查找可能出问题的线程。因为我不知道vs中是否有类似!runaway的命令。如果哪位小伙伴有更好的办法,请一定要留言告诉我!
调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

小提示:按CTRL + ALT + P可以快速打开附加进程界面。

小结

以上三种工具,我会先使用process explorer大体定位下问题,因为可以非常方便的通过Ctrl + Shift + Esc启动。如果用process explorer解决不了,我会根据情况使用windbg或者vs。如果vs正开着(通常是正在写代码的时候),就顺手用vs附加到对应的进程上。如果vs没开着,当然会使用windbg进行排查了。😎

实战代码

如果你想动手实战,复制下面的代码到工程里就可以实战了。
简单介绍下代码:
  • 示例代码中启动了8个线程,是为了增大排查的难度,只有一个线程的情况太简单了。
  • 函数FindFirstRepeatElementIndex()的用途是找到给定的数据中第一次出现重复的数据的索引。
  • 除了我们发现的死循环的问题,还有什么地方可以优化呢?命名,效率,各个方面都可以优化哦,欢迎留言交流。

  1. #include <vector>
  2. #include <future>
  3. #include <iostream>

  4. int FindFirstRepeatElementIndex(bool bExcute)
  5. {
  6.   if (!bExcute)
  7.   {
  8.     return -1;
  9.   }

  10.   int idx = -1;
  11.   std::vector<int> datas = { 1 , 3, 5, 7, 9, 11, 11, 13, 14, 15, 16, 17 };
  12.   for (size_t i = 0; i < datas.size(); ++i)
  13.   {
  14.     for (size_t j = i = 1; j < datas.size(); ++j)
  15.     {
  16.       if (datas[j] == datas)
  17.       {
  18.         idx = i;
  19.         break;
  20.       }
  21.     }
  22.   }

  23.   return idx;
  24. }

  25. #define THREAD_COUNT 8
  26. int main()
  27. {
  28.   std::future<int> results[THREAD_COUNT];

  29.   int realExcuteIdx = rand() % THREAD_COUNT;
  30.   for (int idx = 0; idx < THREAD_COUNT; ++idx)
  31.   {
  32.     bool bRealExcute = (realExcuteIdx == idx);
  33.     results[idx] = std::async(FindFirstRepeatElementIndex, bRealExcute);
  34.   }

  35.   for (auto& one_result : results)
  36.   {
  37.     std::cout<< one_result.get() << std::endl;
  38.   }

  39.   return 0;
  40. }
复制代码

总结
  • 使用process explorer的线程相关功能,在某些情况下,我们甚至可以不用调试器,对照源码就可以找出问题所在。
  • visual studio的并行调用栈可以让我们一次性看到所有线程的调用栈,很是方便。不像Call Stack,每次只能查看一个线程的调用栈。
  • 一般,如果一个线程的运行时间远大于其它线程,这个线程很有可能是与死循环相关的线程。
  • windbg的!runaway命令可以查看每个线程运行的时间,运行时间最长的线程会排在第一位。
  • ~*e ? $tid;.ttime可以查看所有线程的运行时间。
  • ‘~Ns’ 切换到第N号线程。
  • ~~[TID]s 切换到TID对应的线程。

参考资料
  • 《格蠹汇编》
  • 《Windows Sysinternals 实战指南》

评分

参与人数 1驿站币 +2 热心值 +2 收起 理由
58_avatar_small thzzl + 2 + 2 支持原创!

查看全部评分





上一篇:你知道怎么使用DebugView查看内核调试信息吗?
下一篇:今日一贴!将程序隐藏进程,不在任务管理器中出现!
36_avatar_middle
最佳答案
0 
在线会员 发表于 2019-12-22 14:03:02 来自手机 | 显示全部楼层
牛叉 学习了 获得新技能
75_avatar_middle
最佳答案
20 
online_supermod 发表于 2019-12-23 11:28:48 | 显示全部楼层
嘿嘿 有空 学习一下
58_avatar_middle
最佳答案
50 
online_vip 发表于 2019-12-23 16:55:24 | 显示全部楼层
难哥加油呀,我们期待新文章!
70_avatar_middle
最佳答案
0 
ico_lz  楼主| 发表于 2019-12-23 23:26:57 | 显示全部楼层
thzzl 发表于 2019-12-23 16:55
难哥加油呀,我们期待新文章!

感谢支持,有心得一定分享。公众号里有更多的调试文章的,欢迎关注。调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
70_avatar_middle
最佳答案
0 
ico_lz  楼主| 发表于 2019-12-23 23:29:30 | 显示全部楼层
wl1383838438 发表于 2019-12-23 11:28
嘿嘿 有空 学习一下

哈哈,成功惊动版主,欢迎版主指点
70_avatar_middle
最佳答案
0 
ico_lz  楼主| 发表于 2019-12-23 23:31:55 | 显示全部楼层
zsp 发表于 2019-12-22 14:03
牛叉 学习了 获得新技能

你有收获,我就没白写。加油,互相学习,共同进步调试实战——程序CPU占用率飙升,你知道如何快速定位吗?
75_avatar_middle
最佳答案
20 
online_supermod 发表于 2019-12-23 23:50:20 | 显示全部楼层
BianChengNan 发表于 2019-12-23 23:29
哈哈,成功惊动版主,欢迎版主指点

是您的帖子写得好,我也是个学生,大家在VC这个大家庭不用那么客气,还希望您能多多分享您在编程中的经验,让我们少走一下弯路多长一点知识,谢谢
70_avatar_middle
最佳答案
0 
ico_lz  楼主| 发表于 2020-1-10 21:26:35 | 显示全部楼层
wl1383838438 发表于 2019-12-23 23:50
是您的帖子写得好,我也是个学生,大家在VC这个大家庭不用那么客气,还希望您能多多分享您在编程中的经验 ...

厉害!一起交流,共同进步
您需要登录后才可以回帖 登录 | 加入驿站 qq_login

本版积分规则

×【发帖 友情提示】
1、请回复有意义的内容,请勿恶意灌水;
2、纯数字、字母、表情等无意义的内容系统将自动删除;
3、若正常回复后帖子被自动删除,为系统误删的情况,请重新回复其他正常内容或等待管理员审核通过后会自动发布;
4、感谢您对VC驿站一如既往的支持,谢谢合作!

关闭

站长提醒上一条 /2 下一条

QQ|小黑屋|手机版|VC驿站 ( 辽ICP备09019393号-4 )|网站地图wx_jqr

GMT+8, 2021-4-23 04:45

Powered by CcTry.CoM

© 2009-2021 cctry.com

快速回复 返回顶部 返回列表