如何分析在Linux上运行的C++代码?

Gab*_*erg 1732 c++ unix profiling

我有一个在Linux上运行的C++应用程序,我正在优化它.如何确定代码的哪些区域运行缓慢?

Mik*_*vey 1360

如果您的目标是使用分析器,请使用其中一个建议器.

但是,如果你很匆忙,并且你可以手动中断调试器下的程序,而主观速度很慢,那么可以通过一种简单的方法来查找性能问题.

只需暂停几次,每次都看一下调用堆栈.如果有一些代码浪费了一定比例的时间,20%或50%或其他什么,那就是你在每个样本的行为中捕获它的概率.所以这大约是您将看到它的样本的百分比.没有必要的教育猜测.如果您确实猜到了问题所在,这将证明或反驳它.

您可能会遇到不同大小的多个性能问题.如果你清除其中任何一个,剩下的将占用更大的百分比,并且在随后的传球中更容易发现.当在多个问题上复合时,这种放大效应可以导致真正大规模的加速因子.

警告:程序员往往对这种技术持怀疑态度,除非他们自己使用它.他们会说分析器会给你这些信息,但只有当他们对整个调用堆栈进行采样时才会这样,然后让你检查一组随机的样本.(摘要是失去洞察力的地方.)调用图不会给你相同的信息,因为

  1. 他们没有在教学层面总结,并且
  2. 它们在递归的情况下给出令人困惑的摘要.

他们还会说它只适用于玩具程序,实际上它适用于任何程序,并且它似乎在更大的程序上更好地工作,因为它们往往有更多的问题要找.他们会说它有时会发现不是问题的东西,但只有在你看到一次之后才会这样.如果您在多个样本上看到问题,那就是真实的.

PS如果有一种方法可以在某个时间点收集线程池的调用堆栈样本,那么这也可以在多线程程序上完成,就像在Java中一样.

PPS作为一个粗略的概括,您在软件中拥有的抽象层越多,您就越有可能发现这是性能问题的原因(以及获得加速的机会).

补充:它可能不是很明显,但堆栈采样技术在递归的情况下同样有效.原因是通过删除指令节省的时间通过包含它的样本的分数来近似,而不管样本中可能出现的次数.

我经常听到的另一个反对意见是:" 它会在某个地方随机停止,它会错过真正的问题 ".这来自对现实问题的先验概念.性能问题的一个关键属性是他们无视期望.抽样告诉你一些问题,你的第一反应是难以置信.这很自然,但你可以确定它是否发现问题是真的,反之亦然.

补充:让我对其工作原理进行贝叶斯解释.假设有一些指令I(调用或其他)在调用堆栈中占用了一小部分f时间(因此成本太高).为简单起见,假设我们不知道是什么f,但假设它是0.1,0.2,0.3,...... 0.9,1.0,并且每种可能性的先验概率为0.1,因此所有这些成本同样可能先验.

然后假设我们只采集2个堆栈样本,并且我们看到I两个样本的指令,指定观察o=2/2.这给了我们对频率f的新估计I,根据这个:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x | o=2/2)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                
Run Code Online (Sandbox Code Playgroud)

最后一栏说,例如,f> = 0.5 的概率为92%,高于之前假设的60%.

假设先前的假设是不同的.假设我们假设P(f = 0.1)是.991(几乎是确定的),并且所有其他可能性几乎是不可能的(0.001).换句话说,我们先前的确定性I是便宜的.然后我们得到:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x | o=2/2)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                
Run Code Online (Sandbox Code Playgroud)

现在它说P(f> = 0.5)是26%,高于先前假设的0.6%.所以贝叶斯允许我们更新我们对可能成本的估计I.如果数据量很小,它并不能准确地告诉我们成本是多少,只是它足够大,值得修复.

另一种看待它的方法叫做继承规则.如果你将硬币翻了2次,并且两次都出现了硬币,那么它对硬币的可能加权有什么影响呢?值得尊重的回答方式是说它是Beta分布,平均值(命中数+ 1)/(尝试次数+2)=(2 + 1)/(2 + 2)= 75%.

(关键是我们I不止一次看到.如果我们只看到一次,除了f> 0 之外,这并没有告诉我们多少.)

因此,即使是非常少量的样本也可以告诉我们很多关于它看到的指令成本的信息.(而且将看到他们的频率,平均成比例的成本.如果n采取试样,f是成本,那么I将出现在nf+/-sqrt(nf(1-f))样品.实施例,n=10,f=0.3,即3+/-1.4样品).


添加,以直观地感受测量和随机堆栈采样之间的差异:
现在有一些分析器可以对堆栈进行采样,即使是在挂钟时间,但是出现的是测量(或热路径,或热点,从中得到)一个"瓶颈"很容易隐藏).他们没有告诉你(他们很容易)你自己的实际样本.如果您的目标是找到瓶颈,那么您需要查看的数量平均为 2除以所需的时间.因此,如果需要30%的时间,平均2/.3 = 6.7个样本将显示它,并且20个样本显示它的机会是99.2%.

以下是检查测量和检查堆叠样本之间差异的袖口图示.瓶颈可能是这样的一个大块,或许多小块,它没有任何区别.

在此输入图像描述

测量是水平的; 它告诉你特定例程所花费的时间.采样是垂直的.如果有任何方法可以避免整个程序在那一刻所做的事情,如果你在第二个样本上看到它,你就找到了瓶颈.这就是产生差异的原因 - 看到花费时间的全部原因,而不仅仅是花费多少.

  • 这基本上是一个穷人的采样分析器,这很好,但你冒着样本量太小的风险,这可能会给你完全虚假的结果. (279认同)
  • @Crash:我不会讨论"穷人"部分:-)统计测量精度确实需要很多样本,但有两个相互冲突的目标 - 测量和问题定位.我专注于后者,你需要精确的位置,而不是精确的测量.例如,中间堆栈可以有单个函数调用A(); 占50%的时间,但它可以在另一个大型函数B,以及许多其他不昂贵的A()调用.功能时间的精确摘要可以是一个线索,但每个其他堆栈样本将查明问题. (94认同)
  • ......世界似乎认为用呼叫计数和/或平均时间注释的呼叫图就足够了.它不是.而令人遗憾的是,对于那些对调用堆栈进行抽样的人来说,最有用的信息就在他们面前,但为了"统计"的利益,他们会抛弃它. (40认同)
  • 我不是故意不同意你的技术.很明显,我非常依赖堆栈行走采样分析器.我只是指出现在有一些工具以自动方式完成,这一点非常重要,当你将功能从25%提高到15%并需要将其从1.2%降低到0.6%. (28认同)
  • -1:干净的想法,但是如果你在即使是适度的绩效环境中获得报酬,这也浪费了每个人的时间.使用真实的分析器,这样我们就不必在你身后出现并解决实际问题. (8认同)
  • 此外,我倾向于考虑的性能问题不超过程序单个成本的3-4%.我们只是没有比那更大的热点; 相反,我们必须打倒一堆小问题,这样他们才能集体节省大笔开支. (7认同)
  • @ 280Z28:程序员(有时包括我自己)倾向于诋毁不寻常的观点. (7认同)
  • @ 280Z28:也许这不是"真正的"分析(只有43倍的加速):http://stackoverflow.com/questions/926266/performance-optimization-strategies-of-last-resort/927773#927773.缩放是在正确的轨道上.其他"真正的"剖析器容易出现常见的误解:http://stackoverflow.com/questions/1777556/alternatives-to-gprof/1779343#1779343 (6认同)
  • 我怎么能感谢你这个精彩的回复?几个星期以来,我一直在墙上试着用复杂的设置找到问题(mpi->自定义运行时 - >线程等).我刚刚编写了一个简单的脚本,在其中一个节点上重复调用gdb(在我的情况下无关紧要),显示线程,并随机转储其中一个线程的跟踪.反复调用这个脚本我终于能够看到问题所在!我得到了一定程度的改进,事实证明问题是根本的,但也非常容易修复.我爱你Mike Dunlavey!:) (6认同)
  • (请参阅我之前的评论.)对于那些不熟悉gdb的人,你所要做的就是"gdb -batch -x commands ./executable pid",其中命令包含类似(在我的情况下)"info threads"的内容.第一行,"bt"在第二行用于后退跟踪,"q"在最后一行用于退出,甚至可能不需要.我只是运行一个大工作,登录分布式计算机上的一个节点,然后运行该简单脚本.我注意到这个问题是由我的自定义运行时所做的假设造成的,这些假设与我的应用程序代码不兼容.休息是一线修复和胜利!:) (6认同)
  • 大多数现代采样分析器都会为每个样本提供调用堆栈.我特别想到VTune和xbperfview:他们在每个样本中走栈,并准确地向您显示哪些调用者对函数的成本有贡献.当然,没有此功能的采样器的用处要小得多. (5认同)
  • 谢谢.实际上它对递归没有问题.如果调用指令在样本上出现> 1次,那么仍然只有1个样本.指令花费的时间〜=它所在的样本数. (4认同)
  • @Crash:我刚刚查看了 VTune 和 xbperfview 上的文档,我没有看到任何一个声称在每个样本中遍历堆栈,但即使他们这样做,我也没有看到任何一个声称记录,在调用指令级别,包含该调用指令的样本的分数。这与调用图或在调用图级别显示频率非常不同。现在,LukeStackwalker 实际上会在每个样本中遍历堆栈,但是,它丢弃了调用指令的频率!所有这些人都滔滔不绝地谈论“热点”和“瓶颈”,好像这些术语都有明确的定义。 (4认同)
  • 我不确定你得到了什么.如果我想要一个采样分析器显示每个功能的包容性和独占成本,并带有带注释的成本图,我使用xbperfview.如果我想要指令级信息,那么通过CPU的每个操作码都被记录下来,我可以看到哪些确切的PC地址受到了很大影响,我使用的是PIX,但是那么多的数据我只能收集一两帧的价值. (4认同)
  • 我仍然不清楚强调特定呼叫指令的意思.我的采样分析器告诉我,我的程序花费了7%的时间在函数F()中,其中60%来自函数A()中的调用,40%来自函数B()中的调用.在F()下面是G()和H(),当从F()调用时,它们总共加起来为6%,否则加1%.(请参阅dl.getdropbox.com/u/23059/jpix/xbperf.png)我可以使用跟踪工具来获取指令级数据,但通常只是为了查看哪些函数被频繁调用而哪些必须是收紧. (4认同)
  • ......这正是我想要的工具告诉我的.我希望它列出呼叫指令的地址,并告诉我每一个,它在堆栈中的总_inclusive_时间,在感兴趣的间隔中,作为该间隔的百分比.(不是执行计数,不是平均呼叫持续时间.)列表应按该百分比的降序排序,并且只需要显示前100个左右.我将指令的成本定义为该百分比.我知道目前没有工具可以提供这些信息(除了我几年前建立的信息). (3认同)
  • @Mike Dunlavey:这是我在downvoting中的思考过程的引用.我的经验来自并行计算和现在的游戏行业.此外,如果答案被重新评选为详细的真实(科学?)分析建议清单,我不会低估你.这是一个彻底的答案,并且本身绝对正确; 它只是因为没有帮助某人在Linux上寻找C++的高质量分析器(也许是那些已经习惯了微软极其高质量的Windows用户). (3认同)
  • @ 280Z28:够公平的.如果有人正在为Linux寻找高质量的分析器,我认为Zoom就在那里(差不多).我在Windows上没见过类似的,但这不是我的主要工作.MS产品往往具有非常好的质量,但如果他们对提供给您的最有效信息感到困惑,这无济于事.他们仍然会做一些事情,比如只看CPU时间以及我一直试图指出的所有其他东西.无论如何,谢谢你的交流. (3认同)
  • @Mike Dunlavey:Visual Studio 中的默认分析模式(自 2005 年起)是低开销采样分析器。仅限于此,它使 Zoom 显得很初级(或者旋转右网站/Zoom 演示严重缺乏)。一旦你了解了诸如显示跨线程依赖关系和详细的阻塞信息(特定文件 IO、数据库查询等)以及内存分配和对象生命周期跟踪等内容,Microsoft 的功能就非常好。 (3认同)
  • @280Z28:如果你必须过水,你有划艇和奔驰,你选哪个?一种是基本的,另一种具有高质量,但只能按其条款工作。你看这个了吗?http://stackoverflow.com/questions/926266/performance-optimization-strategies-of-last-resort/927773#927773。VS 分析器不能这样做(除非他们对其进行了重大更改)。 (3认同)
  • 这正是[Google cpu-profiler](http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html)的工作原理 - 它会对整个调用堆栈进行采样. (3认同)
  • @klimkin:如果你知道告诉它什么,这是正确的.即使他们说_PROF更好,也更喜欢ITIMER_REAL.如果您知道要问,它似乎会给出行级别%.文本输出有6列 - 忽略1到4.图形输出 - 在真实软件中是一个老鼠的巢和充满节点无关,因为它们不是你的,并且随着递归而变得臃肿.&100 hz样品可能有点矫枉过正. (3认同)
  • @ 280Z28:你现在可能已经忘记了这一点,但在*[sourceforge](http://sourceforge.net/projects/randompausedemo/)*上有一个新的代码示例(在C++中).它有一系列代表加速迭代的代码库,以及一个样本记录,以及一个可以在其中进行操作的短功率点. (3认同)
  • @Waylon:谢谢.它很快.我希望它不那么新颖.它也不脏.我认为,为了定位问题,它比典型的分析器要好得多,因为它可以查明问题而不仅仅是提供线索. (2认同)
  • @Crash:这些示例可能会说明其工作原理:http://stackoverflow.com/questions/890222/analyzing-code-for-efficiency/893272#893272 (2认同)
  • ......他们提供的所有示例都是带有浅层调用堆栈的小程序,其中性能问题确实是"热点",我将其定义为在大部分时间内找到PC的代码,因此必然不包含调用指令.对于调用堆栈上的每个INSTRUCTION,分析器应该做的是,ESPECIALLY调用指令,即包含该指令的样本的分数.在大型软件中,迄今为止最大的性能浪费是可以避免的函数调用,但没有一个分析器"得到它".为什么不??? (2认同)
  • ...我忘了提一下,一般我想在挂钟时间上采样,而不是CPU时间.如果我做得太多,I/OI需要知道它. (2认同)
  • @Norman:我读过Mike Spivey的摘要.他在调用图的上下文中明智地处理递归是件好事.问题是他仍然处于原始gprof的思维模式中,它重视1)功能,而不是指令(丢失状态信息),2)定时准确性,而不是问题定位(需要效率),3)调用图作为摘要(丢失信息),而不是检查代表性的程序状态(即堆栈样本).目标是准确地发现问题,而不是准确地测量问题. (2认同)
  • @280Z28:这种技术存在一些实际问题,例如非常消息驱动的异步情况,其中程序等待的原因很难弄清楚。我还没有发现任何它无法解决的线程内问题,而且大多数分析器通常会完全错过它们(没有仔细的侦查工作)。但我的思想是开放的——你有吗? (2认同)
  • @Mike Dunlavey:由于我的经验,"我投了反对"这个答案中描述的技术对结果的努力平衡要差得多.此外,这篇关于并发性分析的文章很长,但值得一读:http://msdn.microsoft.com/en-us/magazine/ee336027.aspx (2认同)
  • @ 280Z28:是的,当涉及进程间通信时,我不得不求助于其他方法.关于"这个答案中描述的技术对结果的努力平衡要差得多." 我不知道是谁说的,或者他们是否真的尝试过,或者只是事先有意见.就像我说的那样,给我一个例子.计算机科学仍然是一门科学,而不是意见的平衡. (2认同)
  • @Mike:Shark 还可以选择在 IO 等待、系统调用和其他线程状态期间进行采样,并为您提供在分析中包含或排除这些数据点的方法。它将样本归因于函数和特定指令,并允许您应用不同的过滤方法来准确查看执行时间的百分比花在哪里,并且它还允许用户浏览样本。它是一个非常强大的工具并且非常易于使用,但很少有人费心去深入了解它的表面。 (2认同)
  • @Stephen:好的,我印象深刻.我假设短语"花费的时间百分比"实际上意味着"堆叠上的样本百分比"(因为很多人对此有点模糊).仅靠堆栈非常好,但通常[状态背景](http://stackoverflow.com/questions/2473666/tips-for-optimizing-c-net-programs/2474118#2474118)在证明行动是没有必要的. (2认同)
  • @BatchyX:我唯一一次看到这个问题出现在W3.1的VC中,如果你点击"暂停",它就不会停止,直到下一个窗口消息!我们不是在分裂头发.如果我的邻居看到我的狗在5天内在他的院子里放松了三天并问我这件事,我是否有理由说"这并不意味着什么 - 你只有5个样品,他们甚至没有随机时间"?在使方法无效之前,非随机性必须非常糟糕. (2认同)
  • 我把这种技术称为"随机分析".当你试图找出从哪里开始的好工具. (2认同)
  • @djsmith:随心所欲地称呼它,但不要低估它。检查 PDF 幻灯片放映 *[此处,](https://sourceforge.net/projects/randompausedemo/files/)* 其中发现并删除了 *多个“瓶颈”,总计超过原始时间的 **99.8%** .* 代码就在那里。我还没有看到有人使用分析器工具获得类似的加速。 (2认同)
  • @MikeDunlavey:您可以查看http://dtrace.org/blogs/brendan/2011/12/16/flame-graphs/.看起来像一个非常简洁的方式可视化你的意思.现在,如果我能找到一些方法来使用它来可视化callgrind转储...... (2认同)
  • @MikeDunlavey:完全同意,我认为你的方法看起来太简单了,直到我尝试并真正思考我得到了什么!不过,这是我第一次看到有人尝试进行这种可视化。 (2认同)
  • @Electro:那么近还那么远。首先有什么好处:在挂钟时间抓取堆栈样本。那么什么不是(PMP 有同样的问题):你对样本做了什么。您假设您需要一个大数字,因此您假设您需要将它们折叠在一起并计算它们(并且您还丢失了行号信息)。相反,用户应该*看一个并理解它*。然后再看另一个。然后另一个。最多 10 或 20 个。耗时的模式将是显而易见的,并且可能*不会*采用重复多次的整个堆栈样本的形式。*尝试一下。* (2认同)
  • 嗨再次@Thorbjørn:)希望你做得很好.如果你已经尝试过,要解决一个真正的问题,并且以积极的心态,我们可以讨论利弊.我对多线程应用程序的唯一体验是存在繁重的跨线程异步协议.在那种情况下,我的经验是,随机暂停只会让我分道扬.. 为了解决剩下的问题,我必须恢复到日志记录技术. (2认同)
  • 我可能有点慢,迈克,但我已经在几个答案上阅读了你对这种方法的解释,我仍然有点不确定如何将它应用到我的问题领域.我们有一个肥皂网服务,使用太长时间(1-2秒)来计算回报答案.我将如何找到重点关注的内容?根据我的阅读,我会这样做:1.使用Web服务启动我的IDE.2.创建一个针对我的开发框的请求循环3.定期暂停调试器,注意我们在4处暂停的行和源文件.编译一行统计数据命中5. ??? 怎么办? (2认同)
  • @oligofren:带点盐,因为我没有网络应用程序的经验,只有少量线程,有时还有异步协议(网络应用程序).基本问题是为什么*这个时刻*被花费了?忘记统计信息 - 查看每个示例,就好像它是一个可以找到的bug.当你看到它做了两次*时,你可以找到一种方法来避免,修复它并获得加速.异步协议的问题是很难一次性停止所有内容,因此您可以看到为什么要花费这一时刻.然后我使用费力的记录方法. (2认同)
  • @oligofren:有时人们想知道“但这不会错过*真正的*问题吗?” 不用担心。无论“真正的问题”是什么,它都会找到它,即使不是第一次尝试,也会在第二次或第三次尝试时找到它,因为每次加速都会使其他问题更加突出。 (2认同)
  • @oligofren:在第3步.你写的只记下行和源文件.你应该记下迈克在他的回答中解释的整个堆栈轨迹. (2认同)
  • 你好@munesh。我最喜欢的工具是任何调试器,当暂停时,它可以显示每个线程的当前状态。他们中的大多数人只是在等待工作,所以他们并不重要。在重要的线程上,他们可以向我展示所有代码和数据,这样我就可以真正知道为什么要花时间。还有其他工具,例如 *pstack* 或 *lsstack*。就我个人而言,我不同意人们对工具的渴望,除非他们确实做了我需要做的事情。时间不花在收集样本上。时间花在检查和思考每一项上。 (2认同)
  • @MuditJain:我假设您检查了我上面链接的更长的列表,尤其是第 5 点,以及示例链接。试图给出一个更笼统的图景,如果缓慢的错误是对手,你必须打败所有的人,但他们只需要打败你一次,所以如果一个人躲着你,你将无法获得所有的加速. 试图精确测量的分析器会放弃其他信息。因此,如果例程“foo”有 57.34% 的时间在堆栈上,这并不能说明是什么调用链导致了它,或者其中的哪些语句和调用链在它下面占了那个时间。... (2认同)
  • @MuditJain:......如果你不知道是什么调用链导致它,你就无法判断它是否不需要像现在这样被调用。如果您不知道函数中的哪些语句占用了大部分时间,那么您只能猜测如何使其更快。如果您确实知道哪个语句占时间,但不知道它的被调用者,那么您只能猜测是否可以在下面修复任何内容。最好采用调用堆栈并人工查看它们,因为平均而言,任何造成 X% 成本的问题都将出现在 X% 的堆栈上。它无法对你隐瞒。... (2认同)
  • @MuditJain:...但是,作为人类,您只能检查大约 20 个堆栈样本。这意味着,平均而言,速度错误必须大于约 10%。许多明显的错误可能比这更小,但幸运的是,有更大的错误试图隐藏,但无法对您隐藏。当您修复它们时,它们会使较小的变大,因此在重复该过程时更容易找到。测量的问题在于,通过成为目标,一些速度错误可能会隐藏起来。发生这种情况时,程序看起来是最佳的,但实际上不是。坏事。 (2认同)
  • 对我来说,这个过程的重要部分是,它将焦点从程序在哪里花费时间转移到程序为什么在那里花费时间。 (2认同)
  • 有趣的事实:高德纳在给学生的考试评分时采用了这种方法!([讲座 2](https://www-cs-faculty.stanford.edu/~knuth/things.html)) (2认同)
  • 我对这种技术非常怀疑......然后我使用了它!哇。 (2认同)
  • “绩效问题的一个关键特性是它们违背了预期。” 我在 Haskell 程序上尝试了这种技术 - 停止该程序三次,两次它最终进入运行时的线程管理例程。结果,关闭线程使速度提高了 10 倍,这不是我的预期。如果我只依赖 *Haskell* 分析工具,它们会指出 *我的* 代码中的小得多的问题,并且不会报告运行时问题。 (2认同)
  • @phlummox:我看到了你的页面和你对编程语言的兴趣。我是 70 年代的人工智能学生(明斯基)。80 年代初,我在波士顿学院教授计算机科学。在 90 年代,我出版了我的 Opus(一本书名很糟糕的书“构建更好的应用程序”,几乎没有卖出去:)但这些想法效果很好,即使我是世界上最差的推销员。基本上,它说定义一门新语言是一项基本技能,因为无论你是否意识到,你都在这样做,而且最好是意识到这一点并知道是什么让一种语言变得好或不好。我还可以继续... (2认同)

Aja*_*jay 554

您可以使用Valgrind以下选项

valgrind --tool=callgrind ./(Your binary)
Run Code Online (Sandbox Code Playgroud)

它将生成一个名为的文件callgrind.out.x.然后,您可以使用kcachegrind工具来读取此文件.它会给你一个图形分析的结果,比如哪条线的成本是多少.

  • valgrind很棒,但要注意它会使你的程序变慢 (44认同)
  • 还可以查看[Gprof2Dot](http://code.google.com/p/jrfonseca/wiki/Gprof2Dot),了解可视化输出的另一种惊人方法.`./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg` (27认同)
  • @neves是Valgrind在实时分析"gstreamer"和"opencv"应用程序的速度方面不是很有帮助. (2认同)
  • @Sebastian:`gprof2dot`现在在这里:https://github.com/jrfonseca/gprof2dot (2认同)
  • 需要记住的一件事是在包含调试符号但进行优化的情况下进行编译,以获得可探索的东西,但速度特征类似于实际的“发布”构建。 (2认同)

Naz*_*gob 338

我假设你正在使用GCC.标准解决方案是使用gprof进行分析.

确保-pg在分析之前添加到编译中:

cc -o myprog myprog.c utils.c -g -pg
Run Code Online (Sandbox Code Playgroud)

我还没有尝试过,但我听说过google-perftools的消息.绝对值得一试.

相关问题在这里.

其他一些流行语如果gprof不适合你:Valgrind,Intel VTune,Sun DTrace.

  • 比尔,在vaglrind套房,你可以找到callgrind和massif.两者对于分析应用程序非常有用 (68认同)
  • @ Bill-the-Lizard:关于**gprof**的一些评论:http://stackoverflow.com/questions/1777556/alternatives-to-gprof/1779343#1779343 (7认同)
  • gprof -pg只是callstack分析的近似值.它插入mcount调用来跟踪哪些函数正在调用哪些函数.它使用标准时间采样,呃,时间.然后它将在函数foo()中采样的时间分配给foo()的调用者,以支持调用的数量.因此它不区分不同成本的呼叫. (5认同)
  • 我同意gprof是目前的标准.但需要注意的是,Valgrind用于描述程序的内存泄漏和其他与内存相关的方面,而不是速度优化. (3认同)
  • 另见我的gprof警告,http://stackoverflow.com/a/6540100/823636 (2认同)
  • 对于 clang/clang++,人们可以考虑使用 [gperftools](https://github.com/gperftools/gperftools) 的 CPU 分析器。警告:我自己还没有这样做过。 (2认同)

Wil*_*ill 249

较新的内核(例如最新的Ubuntu内核)带有新的'perf'工具(apt-get install linux-tools)AKA perf_events.

这些都带有经典的采样分析器(手册页)以及令人敬畏的时间表!

重要的是,这些工具可以是系统分析而不仅仅是流程分析 - 它们可以显示线程,进程和内核之间的交互,让您了解进程之间的调度和I/O依赖性.

替代文字

  • 很棒的工具!无论如何我有一个典型的"蝴蝶"视图,从"main-> func1-> fun2"风格开始?我似乎无法弄清楚......`perf report`似乎给了我调用父母的函数名...(所以它是一种倒置的蝴蝶视图) (12认同)
  • @kizzx2 - 你可以使用 `gprof2dot` 和 `perf script`。非常不错的工具! (3认同)
  • 即使像4.13这样的新内核也有eBPF用于分析.请访问http://www.brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html和http://www.brendangregg.com/ebpf.html (2认同)
  • 这应该是公认的答案。使用调试器会在样本中引入过多的噪音。linux 的性能计数器适用于多线程、多进程、用户和内核空间,这很棒。您还可以检索许多有用的信息,例如分支和缓存未命中。在@AndrewStern 提到的同一个网站中,有一个火焰图对这种分析非常有用:[火焰图](https://github.com/brendangregg/FlameGraph)。它生成 SVG 文件,可以使用 Web 浏览器打开交互式图形! (2认同)

小智 72

我会使用Valgrind和Callgrind作为我的分析工具套件的基础.重要的是要知道Valgrind基本上是一个虚拟机:

(维基百科)Valgrind本质上是一个使用即时(JIT)编译技术的虚拟机,包括动态重新编译.原始程序中的任何内容都不会直接在主机处理器上运行.相反,Valgrind首先将程序转换为一种称为中间表示(IR)的临时,简单形式,这是一种处理器中立的,基于SSA的形式.在转换之后,在Valgrind将IR转换回机器代码并让主处理器运行它之前,工具(见下文)可以自由地在IR上进行任何转换.

Callgrind是一个构建于此的探查器.主要好处是您不必花费数小时来运行您的应用程序以获得可靠的结果.即使一次运行也足以获得坚如磐石,可靠的结果,因为Callgrind是一个非探测分析器.

建立在Valgrind上的另一个工具是Massif.我用它来分析堆内存使用情况.它很棒.它的作用是为你提供内存使用的快照 - 详细信息什么是内存百分比,以及世界卫生组织把它放在那里.此类信息可在应用程序运行的不同时间点获得.


Tõn*_*uel 63

valgrind --tool=callgrind没有一些选项,运行的答案并不完全.我们通常不想在Valgrind下描述10分钟的慢启动时间,并希望在执行某项任务时对我们的程序进行分析.

所以这就是我推荐的.首先运行程序:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp
Run Code Online (Sandbox Code Playgroud)

现在,当它工作并且我们想要开始分析时,我们应该在另一个窗口中运行:

callgrind_control -i on
Run Code Online (Sandbox Code Playgroud)

这会打开分析.要关闭它并停止整个任务,我们可能会使用:

callgrind_control -k
Run Code Online (Sandbox Code Playgroud)

现在我们在当前目录中有一些名为callgrind.out.*的文件.要查看分析结果,请使用:

kcachegrind callgrind.out.*
Run Code Online (Sandbox Code Playgroud)

我建议在下一个窗口中单击"Self"列标题,否则显示"main()"是最耗时的任务."自我"显示每个功能本身需要花费多少时间,而不是与家属一起.

  • 现在因为某些原因,callgrind.out.*文件总是空的.执行callgrind_control -d对于强制将数据转储到磁盘很有用. (8认同)
  • 不能.我通常的情况类似于整个MySQL或PHP或类似的大事.通常甚至不知道我想先分开什么. (3认同)
  • 或者在我的情况下,我的程序实际上将一堆数据加载到LRU缓存中,我想不要对其进行分析.因此,我在启动时强制加载缓存的子集,并仅使用该数据来分析代码(让OS + CPU管理缓存中的内存使用).它可以工作,但加载缓存是缓慢的,并且我试图在不同的上下文中分析代码的CPU密集,因此callgrind会产生严重污染的结果. (2认同)
  • 还有`CALLGRIND_TOGGLE_COLLECT`以编程方式启用/禁用收集;参见/sf/answers/959057221/ (2认同)
  • @TõnuSamuel,对我来说 callgrind.out.* 也是空的。就我而言,程序在分析时崩溃了。一旦解决了崩溃的原因,我就可以看到 callgrind.out.* 文件中的内容。 (2认同)

Rob*_*its 56

这是对Nazgob的Gprof回答的回应.

我在过去的几天里一直在使用Gprof,并且已经发现了三个重要的限制,其中一个我还没有在其他任何地方看到过记录:

  1. 除非您使用变通方法,否则它在多线程代码上无法正常工作

  2. 调用图被函数指针搞糊涂了.示例:我有一个函数调用multithread(),它使我能够在指定的数组上多线程化指定的函数(都作为参数传递).然而,Gprof将所有调用multithread()视为等效于计算儿童时间的目的.由于我传递的某些功能multithread()比其他功能要长得多,因此我的调用图大多没用.(对于那些想知道线程是否是问题的人:不,multithread()可以选择,并且在这种情况下,只在调用线程上顺序运行所有内容).

  3. 在这里说"......呼叫数字是通过计数而非采样得出的.它们是完全准确的......".然而我发现我的调用图给了我5345859132 + 784984078作为我最常调用的函数的调用统计数据,其中第一个数字应该是直接调用,第二个递归调用(它们都来自自身).由于这暗示我有一个错误,我将长(64位)计数器放入代码并再次执行相同的操作.我的计数:5345859132直接和78094395406自我递归调用.那里有很多数字,所以我会指出我测量的递归调用是780亿,而Gprof是784m:100不同.两次运行都是单线程和未经优化的代码,一个是编译的,另一个是编译-g-pg.

这是在64位Debian Lenny下运行的GNU Gprof(GNU Binutils for Debian)2.18.0.20080103,如果这有助于任何人.


Cir*_*四事件 36

C++分析技术综述:gprof vs valgrind vs perf vs gperftools

在这个答案中,我将使用几种不同的工具来分析一些非常简单的测试程序,以便具体比较这些工具的工作原理。

以下测试程序非常简单,并执行以下操作:

  • main电话fastmaybe_slow3 次,其中一个maybe_slow电话很慢

    maybe_slow如果我们考虑对子函数的调用, 的慢调用时间要长 10 倍,并且在运行时占主导地位common。理想情况下,分析工具将能够将我们指向特定的慢速调用。

  • fastmaybe_slow呼叫common,占大部分的程序执行的

  • 程序界面为:

    ./main.out [n [seed]]
    
    Run Code Online (Sandbox Code Playgroud)

    并且程序O(n^2)总共执行循环。seed只是为了在不影响运行时获得不同的输出。

主文件

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
    for (uint64_t i = 0; i < n; ++i) {
        seed = (seed * seed) - (3 * seed) + 1;
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
    uint64_t max = (n / 10) + 1;
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
    uint64_t max = n;
    if (is_slow) {
        max *= 10;
    }
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

int main(int argc, char **argv) {
    uint64_t n, seed;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    if (argc > 2) {
        seed = strtoll(argv[2], NULL, 0);
    } else {
        seed = 0;
    }
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 1);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    printf("%" PRIX64 "\n", seed);
    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

教授

gprof 需要使用仪器重新编译软件,并且它还与该仪器一起使用采样方法。因此,它在准确性(采样并不总是完全准确并且可以跳过函数)和执行速度减慢(检测和采样是相对较快的技术,不会大大减慢执行速度)之间取得平衡。

gprof 内置在 GCC/binutils 中,所以我们要做的就是使用-pg启用 gprof的选项进行编译。然后,我们使用 size CLI 参数正常运行程序,该参数会产生几秒钟的合理持续时间 ( 10000):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000
Run Code Online (Sandbox Code Playgroud)

出于教育原因,我们还将在不启用优化的情况下运行。请注意,这在实践中是无用的,因为您通常只关心优化优化程序的性能:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000
Run Code Online (Sandbox Code Playgroud)

首先,time告诉我们有和没有的执行时间-pg相同,这很棒:没有减速!然而,我已经看到复杂软件的 2 到 3 倍减速,例如这张票所示

因为我们用 编译-pg,所以运行程序会生成一个gmon.out包含分析数据的文件。

我们可以gprof2dot按照以下问题以图形方式观察该文件:Is it possible to get a graphics representation of gprof results?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg
Run Code Online (Sandbox Code Playgroud)

在这里,该gprof工具读取gmon.out跟踪信息,并在 中生成人类可读的报告main.gprofgprof2dot然后读取以生成图形。

gprof2dot 的来源是:https : //github.com/jrfonseca/gprof2dot

我们观察到以下-O0运行:

在此处输入图片说明

并为-O3运行:

在此处输入图片说明

-O0输出几乎是不言自明的。例如,它显示 3 个maybe_slow调用及其子调用占用了总运行时间的 97.56%,尽管maybe_slow没有子项的自身执行占总执行时间的 0.00%,即几乎所有时间花在该函数上孩子打电话。

TODO:为什么输出中main缺少-O3,即使我可以bt在 GDB 中看到它?GProf 输出中缺少函数我认为这是因为 gprof 除了其编译的仪器之外还基于采样,而且-O3 main速度太快并且没有样本。

我选择 SVG 输出而不是 PNG,因为可以使用Ctrl+搜索 SVG,F并且文件大小可以小 10 倍左右。此外,对于复杂的软件,生成的图像的宽度和高度可能会非常大,有数万个像素,eog在这种情况下,GNOME 3.28.1 对于 PNG 会出错,而 SVG 会被我的浏览器自动打开。不过,gimp 2.8 运行良好,另请参阅:

但即便如此,您仍将大量拖动图像以找到您想要的内容,例如,请参阅从这张票中获取的“真实”软件示例中的此图像:

在此处输入图片说明

在所有这些细小的未排序的意大利面条行相互重叠的情况下,您能轻松找到最关键的调用堆栈吗?dot我确定可能有更好的选择,但我现在不想去那里。我们真正需要的是一个合适的专用查看器,但我还没有找到:

但是,您可以使用颜色图稍微缓解这些问题。比如,在之前的巨幅图像上,当我做出绿色在红色之后,最后是越来越深的蓝色的精彩演绎时,我终于找到了左边的关键路径。

或者,我们也可以观察gprof我们之前保存在的内置 binutils 工具的文本输出:

cat main.gprof
Run Code Online (Sandbox Code Playgroud)

默认情况下,这会产生一个非常详细的输出,解释输出数据的含义。由于我无法解释得更好,我会让你自己阅读。

一旦你理解了数据输出格式,你可以减少冗长,只显示没有教程的数据-b选项:

gprof -b main.out
Run Code Online (Sandbox Code Playgroud)

在我们的示例中,输出用于-O0

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
100.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common                  [4] fast                    [3] maybe_slow
Run Code Online (Sandbox Code Playgroud)

-O3

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
100.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common
Run Code Online (Sandbox Code Playgroud)

作为每个部分的非常快速的总结,例如:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
Run Code Online (Sandbox Code Playgroud)

以左缩进的函数为中心 ( maybe_flow)。[3]是该函数的 ID。函数上方是调用者,下方是被调用者。

对于-O3,请参见此处,就像在图形输出中一样,maybe_slow并且fast没有已知的父级,这就是文档所说的<spontaneous>意思。

我不确定是否有一种很好的方法可以使用 gprof 进行逐行分析:`gprof` 在特定代码行上花费的时间

valgrind callgrind

valgrind 通过 valgrind 虚拟机运行程序。这使得分析非常准确,但它也产生了非常大的程序减速。我之前也提到过 kcachegrind:获取代码的图形函数调用图的工具

callgrind 是 valgrind 用于分析代码的工具,kcachegrind 是一个 KDE 程序,可以将 cachegrind 输出可视化。

首先,我们必须删除-pg标志以返回正常编译,否则运行实际上会失败Profiling timer expired,并且是的,这很常见,我这样做了,并且有一个堆栈溢出问题。

所以我们编译并运行:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000
Run Code Online (Sandbox Code Playgroud)

我启用--dump-instr=yes --collect-jumps=yes是因为这也转储了信息,使我们能够以相对较小的额外开销成本查看每条装配线的性能细分。

time马上,告诉我们程序执行时间为 29.5 秒,因此在此示例中我们的速度降低了大约 15 倍。显然,这种放缓将成为更大工作负载的严重限制。在此处提到的“真实世界软件示例”,我观察到速度下降了 80 倍。

运行生成一个配置文件数据文件,callgrind.out.<pid>例如callgrind.out.8554在我的情况下。我们查看该文件:

kcachegrind callgrind.out.8554
Run Code Online (Sandbox Code Playgroud)

它显示了一个 GUI,其中包含类似于文本 gprof 输出的数据:

在此处输入图片说明

此外,如果我们进入右下角的“调用图”选项卡,我们会看到一个调用图,我们可以通过右键单击它来导出该调用图以获得以下带有不合理数量的白色边框的图像:-)

在此处输入图片说明

我认为fast没有显示在该图表上,因为 kcachegrind 必须简化了可视化,因为该调用占用的时间太少,这可能是您在真实程序中想要的行为。右键菜单有一些设置来控制何时剔除此类节点,但在快速尝试后我无法让它显示如此短的调用。如果我单击fast左侧窗口,它会显示带有 的调用图fast,因此实际上捕获了该堆栈。还没有人找到一种方法来显示完整的图形调用图:使 callgrind 显示 kcachegrind调用图中的所有函数调用

TODO 在复杂的 C++ 软件上,我看到了一些 type 的条目<cycle N>,例如<cycle 11>我期望函数名的地方,这是什么意思?我注意到有一个“循环检测”按钮可以打开和关闭它,但这是什么意思?

perflinux-tools

perf似乎只使用 Linux 内核采样机制。这使得设置非常简单,但也不完全准确。

sudo apt install linux-tools
time perf record -g ./main.out 10000
Run Code Online (Sandbox Code Playgroud)

这为执行增加了 0.2 秒,所以我们在时间上很好,但在common使用键盘右箭头扩展节点后,我仍然没有看到太多兴趣:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              
Run Code Online (Sandbox Code Playgroud)

然后我尝试对-O0程序进行基准测试,看看它是否显示任何内容,直到现在,我才看到调用图:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              
Run Code Online (Sandbox Code Playgroud)

TODO:-O3执行过程中发生了什么?难道仅仅是maybe_slowfast是太快了,并没有得到任何的样本?它是否适用-O3于需要更长执行时间的大型程序?我错过了一些 CLI 选项吗?我发现-F要控制赫兹的采样频率,但我将它调高到默认允许的最大值-F 39500(可以用 增加sudo),但我仍然看不到清晰的调用。

一个很酷的事情perf是来自 Brendan Gregg 的 FlameGraph 工具,它以一种非常简洁的方式显示调用堆栈时序,让您可以快速查看大调用。该工具可在:https://github.com/brendangregg/FlameGraph和还提到了他PERF教程:http://www.brendangregg.com/perf.html#FlameGraphs当我跑perf没有sudoERROR: No stack counts found这样的现在我将这样做sudo

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg
Run Code Online (Sandbox Code Playgroud)

但在这样一个简单程序的输出不是很容易理解,因为我们不能很容易地看到没有maybe_slow,也没有fast上图:

在此处输入图片说明

在一个更复杂的例子中,图表的含义变得很清楚:

在此处输入图片说明

TODO[unknown]在那个例子中有一个函数日志,为什么?

另一个可能值得的性能 GUI 界面包括:

  • Eclipse Trace Compass 插件:https : //www.eclipse.org/tracecompass/

    但是,这是你必须首先将数据转换到通用跟踪格式,这是可以做到的缺点perf data --to-ctf,但它需要在构建时启用/有perf足够新,无论其是不是为PERF的情况下Ubuntu 18.04

  • https://github.com/KDAB/hotspot

    这样做的缺点是似乎没有 Ubuntu 包,构建它需要 Qt 5.10,而 Ubuntu 18.04 是 Qt 5.9。

gperftools

以前称为“Google Performance Tools”,来源:https : //github.com/gperftools/gperftools基于示例。

首先安装 gperftools:

sudo apt install google-perftools
Run Code Online (Sandbox Code Playgroud)

然后,我们可以通过两种方式启用 gperftools CPU 分析器:在运行时或在构建时。

在运行时,我们必须通过 set LD_PRELOADto point to libprofiler.so,你可以找到它locate libprofiler.so,例如在我的系统上:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000
Run Code Online (Sandbox Code Playgroud)

或者,我们可以在链接时构建库,LD_PRELOAD在运行时分配传递:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000
Run Code Online (Sandbox Code Playgroud)

另请参阅:gperftools - 配置文件未转储

到目前为止,我发现查看此数据的最佳方法是使 pprof 输出与 kcachegrind 作为输入的格式相同(是的,Valgrind-project-viewer-tool)并使用 kcachegrind 查看:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out
Run Code Online (Sandbox Code Playgroud)

使用这两种方法中的任何一种运行后,我们都会得到一个prof.out配置文件数据文件作为输出。我们可以通过以下方式以图形方式将该文件视为 SVG:

google-pprof --web main.out prof.out
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

它像其他工具一样提供了一个熟悉的调用图,但使用笨重的样本数单位而不是秒。

或者,我们也可以通过以下方式获取一些文本数据:

google-pprof --text main.out prof.out
Run Code Online (Sandbox Code Playgroud)

这使:

Using local file main.out.
Using local file prof.out.
Total: 187 samples
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow
Run Code Online (Sandbox Code Playgroud)

另请参阅:如何使用谷歌性能工具

使用原始perf_event_open系统调用检测您的代码

我认为这与使用的底层子系统相同perf,但您当然可以通过在编译时使用感兴趣的事件显式检测程序来获得更大的控制。

这对大多数人来说可能太硬核了,但它很有趣。Minimal runnable example at:

  • 默认情况下,perf record 使用帧指针寄存器。现代编译器不记录帧地址,而是将寄存器用作通用用途。另一种方法是使用 `-fno-omit-frame-pointer` 标志进行编译或使用不同的替代方案:根据您的场景使用 `--call-graph "dwarf"` 或 `--call-graph "lbr"` 进行记录。 (4认同)

  • Ehs*_*san 23

    使用Valgrind,callgrind和kcachegrind:

    valgrind --tool=callgrind ./(Your binary)
    
    Run Code Online (Sandbox Code Playgroud)

    生成callgrind.out.x.使用kcachegrind读取它.

    使用gprof(add -pg):

    cc -o myprog myprog.c utils.c -g -pg 
    
    Run Code Online (Sandbox Code Playgroud)

    (多线程,函数指针不太好)

    使用google-perftools:

    使用时间采样,显示I/O和CPU瓶颈.

    英特尔VTune是最好的(免费用于教育目的).

    其他: AMD Codeanalyst(自AMD CodeXL取代),OProfile,'perf'工具(apt-get install linux-tools)


    rao*_*lla 6

    另外值得一提的是

    1. HPCToolkit ( http://hpctoolkit.org/ ) - 开源,适用于并行程序并有一个 GUI 可以通过多种方式查看结果
    2. 英特尔 VTune ( https://software.intel.com/en-us/vtune ) - 如果您有英特尔编译器,这是非常好的
    3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

    我使用过 HPCToolkit 和 VTune,它们在寻找帐篷中的长杆方面非常有效,并且不需要重新编译您的代码(除非您必须在 CMake 中使用 -g -O 或 RelWithDebInfo 类型构建以获得有意义的输出) . 我听说 TAU 的功能相似。


    fwy*_*ard 5

    对于单线程程序,可以使用igprof,即Ignominous Profiler:https ://igprof.org/ 。

    它是一个采样探查器,遵循... long ...的答案,由Mike Dunlavey回答,它将把结果包装在可浏览的调用堆栈树中,并在每个函数中花费的时间或内存进行注释,无论是累积的还是每个功能。


    u__*_*u__ 5

    实际上有点惊讶没有多少人提到google/benchmark,虽然固定代码的特定区域有点麻烦,特别是如果代码库有点大,但是我发现这在与callgrind

    恕我直言,识别造成瓶颈的部分是这里的关键。不过,我会首先尝试回答以下问题,然后根据该问题选择工具

    1. 我的算法正确吗?
    2. 是否存在被证明是瓶颈的锁?
    3. 是否有特定的代码部分被证明是罪魁祸首?
    4. IO 处理和优化怎么样?

    valgrind结合callgrindkcachegrind应该对上述几点提供一个不错的估计,一旦确定代码的某些部分存在问题,我建议做一个微基准测试 - 这google benchmark是一个很好的起点。