我有一个C++应用程序,运行在Linux上,这是我在优化的过程。如何确定代码中哪些区域运行缓慢?
- 如果您将提供更多关于开发堆栈的数据,您可能会得到更好的答案。有来自Intel和Sun的分析程序,但您必须使用它们的编译器。这是一个选择吗?
- 它已经在以下链接上得到了回答:stackoverflow.com/questions/2497211/…
- 大多数答案都是code的简介。然而,优先级反转、缓存别名、资源争用等都是优化和性能的因素。我认为人们会把信息读进我的慢代码中。常见问题解答正在引用此线程。
- CPPCon 2015:Chandler Carruth"调谐C++:基准、CPU和编译器!"哦,我的天!"
- 我曾经随机使用pstack,大部分时间会打印出最典型的堆栈,其中程序的大部分时间都在那里,因此指向了瓶颈。
- cachegrind/callgrind?
如果您的目标是使用分析器,请使用建议的分析器之一。好的。
但是,如果您很着急,并且可以在调试器主观性缓慢的情况下手动中断程序,那么有一种简单的方法可以发现性能问题。好的。
只需暂停几次,每次查看调用堆栈。如果有一些代码浪费了一定比例的时间,20%或50%或其他什么,那就是您在对每个样本执行操作时捕获它的概率。这大概就是你将看到它的样本的百分比。不需要经过受过教育的猜测。如果你确实对问题是什么有了猜测,这将证明或反驳它。好的。
您可能有多个不同大小的性能问题。如果你清除了其中的任何一个,剩下的将占更大的百分比,并且在随后的传球中更容易被发现。这种放大效应,当在多个问题上复合时,会导致真正巨大的加速因素。好的。
警告:程序员往往对这种技术持怀疑态度,除非他们自己使用过它。他们会说profiler提供了这些信息,但是只有当他们对整个调用堆栈进行采样,然后让您检查随机的一组样本时,这才是正确的。(摘要是失去洞察力的地方。)调用图不能提供相同的信息,因为好的。
它们不在说明级别进行总结,并且
它们在递归的情况下给出了令人困惑的摘要。
他们也会说,它只适用于玩具程序,而实际上它适用于任何程序,而且在更大的程序上似乎效果更好,因为他们往往会发现更多的问题。他们会说,有时候它会发现一些不是问题的东西,但只有当你看到一件事时,这才是真的。如果您在多个样本上看到问题,那么它是真实的。好的。
P.S.这也可以在多线程程序上完成,如果有一种方法在一个时间点收集线程池的调用堆栈样本,就像Java中那样。好的。
P.P.S作为一个粗略的概括性,您在软件中拥有的抽象层越多,您越有可能发现这是性能问题的原因(以及加快速度的机会)。好的。
补充:这可能不明显,但在递归的情况下,堆栈采样技术同样有效。原因是,删除指令所节省的时间近似于包含指令的样本的分数,而不管指令在样本中可能出现的次数是多少。好的。
我经常听到的另一个反对意见是:"它会随机停止某个地方,并且会错过真正的问题。"这来自于对真正的问题有一个先验的概念。性能问题的一个关键特性是它们违背了预期。抽样会告诉你问题所在,你的第一反应就是不相信。这是很自然的,但你可以确定如果它发现了问题,它是真实的,反之亦然。好的。
补充:让我对它的工作原理做一个贝叶斯解释。假设有一些指令I(call或其他)在调用堆栈上,它是时间的一部分f(因此要花费那么多)。为了简单起见,假设我们不知道f是什么,但假设它是0.1、0.2、0.3,…。0.9,1.0,每种可能性的先验概率为0.1,因此所有这些成本都同样可能是先验的。好的。
然后假设我们只取了两个叠加样本,我们在两个样本上都看到了说明I,即指定观测o=2/2。这给了我们对I的f频率的新估计,根据此:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 |
最后一篇专栏文章说,例如,f>=0.5的概率是92%,高于先前假设的60%。好的。
假设前面的假设是不同的。假设我们假设p(f=0.1)是.991(几乎可以确定),而所有其他的可能性几乎是不可能的(0.001)。换句话说,我们先前确定的是,I是便宜的。然后我们得到:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 |
现在它说p(f>=0.5)是26%,高于先前假设的0.6%。因此,Bayes允许我们更新我们对I可能成本的估计。如果数据量很小,它就不能准确地告诉我们成本是多少,只是它足够大,值得修复。好的。
另一种看待这个问题的方法是继承规则。如果你把一枚硬币掷两次,两次都是正面朝上,那么硬币的可能重量又能说明什么呢?值得尊敬的回答方法是说它是一个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%。好的。
这是一个现成的说明检查测量和检查堆叠样品之间的差异。瓶颈可能是这样的一个大斑点,也可能是许多小斑点,这没有区别。好的。
好的。
测量是水平的,它告诉你特定于时间的例行程序需要多少时间。取样是垂直的。如果有任何方法可以避免整个程序此时正在做的事情,并且如果在第二个示例中看到它,那么就发现了瓶颈。这就是造成差异的原因——看到花费时间的全部原因,而不仅仅是多少。好的。好啊。
- 我是一个程序员,我在一个性能很重要的环境中工作。我得说这是一种很好的技巧(这就是为什么我要反对你的原因)。但是你能为递归程序做些什么呢?不是很多。这就是为什么我坚持理性量化这是一个很好的工具的原因。
- 谢谢。实际上,它对递归没有问题。如果一个样本上出现的调用指令大于1次,则仍然只有1个样本。一条指令花费的时间~=它打开的样本数。
- 这看起来像是一个新的快速和肮脏的替代分析。谢谢你的建议。
- @韦伦:谢谢。它很快。我希望它不是那么新颖。它也不脏。我认为,为了定位问题,它比典型的轮廓仪要好得多,因为它能精确定位问题,而不仅仅是提供线索。
- @迈克也许在我试过之后,我会同意这件事的。
- 这基本上是一个穷人的抽样分析,这是伟大的,但你运行的风险太小的样本大小,这可能会给你完全虚假的结果。
- @崩溃:我不会讨论"穷人"的部分:确实,统计测量精度需要许多样本,但有两个相互冲突的目标-测量和问题定位。我关注的是后者,你需要的是定位精度,而不是测量精度。例如,在堆栈中间,可以有一个函数调用a();这占了50%的时间,但它可以在另一个大型函数b中,以及许多其他对a()的调用,这些调用并不昂贵。函数时间的精确总结可能是一个线索,但其他每一个堆栈示例都会指出问题所在。
- @crash:这些示例可能会说明它的工作原理:stackoverflow.com/questions/890222/…
- …不是对Belabor说,但是如果有人说A浪费了一点时间,比如15%,那么15%的时间程序处于"有罪状态"。每个样本有15%的概率捕捉到它。取20个样本,平均3个堆栈将包含一个,也许2个,也许更多。如果只有1或0个样本包含它,那么您会错过它,否则您会看到它。现在,您还没有准确地测量问题,但是您已经确定了问题的位置,如果您解决了问题,您将节省15%。
- 大多数现代采样分析程序都会为每个样本提供调用堆栈。我特别考虑的是vtune和xbperfview:它们在每个示例中遍历堆栈,并向您确切显示哪些调用者对函数的成本有贡献。当然,没有这个特性的采样器用处要小得多。
- @崩溃:我刚刚看了一下vtune和xbperfview上的文档,没有看到任何一个声明要在每个示例中遍历堆栈,但是即使是这样,我也没有看到任何一个声明要在调用指令级别记录包含该调用指令的示例部分。这与调用图或在调用图级别显示频率非常不同。现在,lukestackwalker确实在每个样本处遍历堆栈,但随后,它将丢弃调用指令的频率!所有这些人都在谈论"热点"和"瓶颈",就好像这些术语定义得很好。
- …他们给出的所有例子都是带有浅调用堆栈的小程序,其中性能问题实际上是"热点",我将其定义为在很大一部分时间内发现PC的代码,因此不一定包含调用指令。分析程序应该为调用堆栈上的每个指令,特别是调用指令,提供包含该指令的样本部分。在大型软件中,到目前为止最大的性能浪费是可避免的函数调用,但是没有一个评测人员"明白"。为什么不呢????
- …全世界似乎都认为用调用计数和/或平均时间注释的调用图已经足够好了。不是这样。可悲的是,对于那些对调用堆栈进行抽样的人来说,最有用的信息就在他们面前,但是为了"统计数据",他们把它扔掉了。
- 我不知道你在说什么。如果我想让一个采样分析器显示每个函数的包含和独占成本,以及一个带注释的成本图,我可以使用XBperfView。如果我想要指令级的信息,通过CPU的每一个操作码都会被记录下来,我可以看到哪个确切的PC地址会被大量点击,我使用pix,但是这是太多的数据了,我只能收集一两帧。
- 另外,我倾向于看的性能问题,每个项目的整体成本不超过3-4%。我们只是没有比这更大的热点;相反,我们必须解决一些小问题,这样它们就可以共同节省大量资金。
- @崩溃:嗯,可能是你的代码太紧了。我不记得我是否吸引了你的注意:stackoverflow.com/questions/926266/…主要的一点是,在大尺寸的软件中,可避免的函数调用大大超过了PC的任何功能。函数并不小,如果它们调用另一个函数,那么它们不一定在少数地方(甚至在源代码中是可见的)这样做,因此知道包含时间充其量只是一个提示,而独占时间大部分为零,除了叶。…
- …但是,每个堆栈示例都会精确定位调用指令,如果调用指令(不是指令本身,而是函数返回之前的时间)在堆栈上的时间百分比(如10%),那么它将显示在该百分比的示例上(平均),这将告诉您通过杀死它可以保存什么。这种对PC级优化的关注缺少了广泛的可避免的函数调用,而这些调用并不是热点。我把函数调用比作信用卡。这张卡本身不花什么钱,但真正的花费是它很容易花太多钱。
- …如今,分析人员被称为"呼叫树"。这是一个开始,因为在调用树的每个点上,它都可以指示调用函数的确切语句。问题是,该语句可以存在于调用树的多个分支中,因此它在任何一个分支中的开销都不能代表它真正的开销。
- …这正是我想要的工具告诉我的。我希望它列出调用指令的地址,并告诉我每个调用指令在堆栈上的总包含时间(在感兴趣的时间间隔内),作为该时间间隔的百分比。(不是执行计数,不是平均调用持续时间。)列表应该按该百分比降序排序,并且只需要显示前100个左右的值。我把指令的成本定义为那个百分比。我不知道现在有什么工具可以提供这些信息(除了几年前我建立的工具)。
- …如果你想提出递归,我很乐意为无数次解释为什么这不是一个问题。
- 我仍然不清楚你所说的强调特定的呼叫指令是什么意思。我的采样分析器告诉我,我的程序在函数f()中花费了7%的时间,其中60%来自函数a()中的调用,40%来自函数b()中的调用。在f()下是g()和h(),当从f()调用时,它们加起来占帧的6%,否则加起来占1%。(请参阅dl.getdropbox.com/u/23059/jpix/xbperf.png)我可以使用跟踪工具来获取指令级数据,但通常不需要只查看哪些函数被频繁调用,哪些函数必须被收紧。
- 我不是不同意你的技术。很明显,我非常依赖于堆栈行走采样分析器。我只是指出,现在有一些工具可以自动完成这项工作,当您超过了从25%到15%获取函数的临界点,需要将其从1.2%降到0.6%时,这一点非常重要。
- @谢谢你陪我在这里。按照您的示例,函数f在7%的时间内位于堆栈上,而从a调用f的时间在4.2%的堆栈上。现在,如果幸运的话,A只在一个站点中调用F,并且该站点在源代码中可见。如果从多个站点调用f,或者如果调用被编译器插入,您仍然需要使用脑力来定位占4.2%大部分的站点,这些站点在堆栈上是可用的。我上面给你的链接显示了这一点的含义。如果我能收集1000个样品,我会很乐意的,但是没有样品就可以了。
- …我忘了提一下,一般来说我想采样墙上的时钟时间,而不是CPU时间。如果我做了太多的I/O,我需要知道。
- …另一种情况是,堆栈采样确实会将您从煎锅中拉出来:通知式编程,其中有要保持同步的数据层,并且"更改处理程序"会即时添加(然后忘记,然后重新添加)。同样的情况也适用于"属性",在"属性"中,您不知道当您获取/设置某些东西时会发生多少,特别是在虚拟类中。
- MikeSpivey在gprof上有一个非常好的变体,它可以敏感地处理递归。我讨厌我们必须忍受的GNU项目。糟糕的工具!呸!
- @诺曼:我读了迈克·斯皮维的摘要。他在调用图的上下文中明智地处理递归是很好的。问题在于,他仍然处于最初的gprof的思维模式中,它的价值观是1)函数,而不是指令(丢失状态信息),2)时间的准确性,而不是问题的位置(需要效率),3)调用图作为摘要(丢失信息),而不是检查代表性的程序状态(即堆栈样本)。目标是准确地发现问题,而不是准确地测量。
- …下面是一个更好的解释:stackoverflow.com/questions/406760/…
- @迈克,我完全同意你关于抽样分析的看法。我喜欢oprofile,当它在任何代码上都表现出色时,它作为内核配置文件会受到很坏的批评。它的工作原理与您随机停止调试器的想法相同,但它使用硬件中断和性能计数器。它还可以提供调用堆栈信息。当性能瓶颈是malloc时,这是非常宝贵的,但真正的问题是一个不必要分配的紧密循环。
- @凯斯宾:我不是所有简介的专家,但我很高兴你喜欢奥洛菲勒。许多人接近目标,但错过了目标,例如只在CPU时间采样,从而错过了由不必要的I/O组成的所有优化机会。或者他们坚持查看函数的想法,而不是代码行,从而不必要地丢弃信息,并被捕获在调用图/递归/自时tarpit中。他们被卷入了"测量精度"和"效率"的泥潭中。
- -1:好主意,但是如果你在一个适度的以性能为导向的环境中工作得到报酬,这是在浪费每个人的时间。使用一个真正的分析器,这样我们就不必跟在你后面解决实际的问题。
- @280Z28:也许这不是"真实"的分析(只有43倍加速):stackoverflow.com/questions/926266/…。缩放在正确的轨道上。其他"真实"的剖析者也容易出现一些常见的神话:stackoverflow.com/questions/1777556/gprof/hellip的替代品;
- @280Z28:程序员(有时包括我自己)有一种否认不寻常观点的倾向。
- @280Z28:这项技术确实存在一些问题,例如消息驱动的异步情况,在这种情况下,程序等待的原因很难梳理出来。我没有发现线内的任何问题,它无法钉住,而且往往大多数轮廓完全错过他们(没有仔细的侦查工作)。但我的思想是开放的-你有吗?
- @迈克·邓拉维:我投了反对票,因为根据我的经验,"这个答案中所描述的技术比平均水平的结果平衡差得多。"此外,关于并发分析的这篇文章很长,但值得一读:msdn.microsoft.com/en-us/magazine/ee336027.aspx
- @280Z28:是的,当涉及到进程间通信时,我不得不求助于其他方法。关于"这个答案中所描述的技术比平均的结果平衡要差得多。"我不知道是谁说的,或者他们是否真的尝试过,或者只是有一个事先的意见。就像我说的,给我一个例子。计算机科学仍然是一门科学,而不是观点的平衡。
- @迈克·邓拉维:这是我在投反对票时的思维过程中引用的一句话。我的经验来自并行计算,现在在游戏行业。另外,如果答案比一份关于真实(科学?)的详细建议更重要的话,我也不会投你反对票。仿形。这是一个彻底的答案,绝对正确,在它自己的权利,它只是不帮助别人寻找一个质量分析器为C++的Linux(也许有人谁习惯了微软的极高质量的一个Windows)。
- @280Z28:够公平的。如果有人在寻找一个高质量的Linux分析器,我认为ZOOM就在那里(差不多)。我在窗户上没有看到类似的,但这不是我的主要工作。微软的产品往往质量很好,但如果他们对什么是最有效的信息感到困惑,那就没有帮助了。他们仍然在做一些事情,比如只看CPU时间,以及我一直试图指出的所有其他事情。不管怎样,谢谢你的交换。
- @Mike Dunlavey:Visual Studio中的默认分析模式(自2005年以来)是一个低开销的采样探查器。仅限于此,它会使缩放看起来很初级(或者Rotateright网站/缩放演示严重缺乏)。一旦你涉及到显示跨线程依赖关系和详细的阻塞信息(特定的文件IO、数据库查询等),以及内存分配和对象生存期跟踪,那么微软的这一款就非常好了。
- @280Z28:如果你要过一些水,你有一艘划艇和一辆奔驰,你会选择哪一辆?一种是初级的,另一种是高质量的,但只能按其条件工作。你看过这个吗?stackoverflow.com/questions/926266/…。vs剖析器无法做到这一点(除非他们对它做了重大更改)。
- @迈克·邓拉维:如果你不介意的话,我不想通过阅读你对这个问题的讨论而产生偏见——我想使用原始代码,并严格使用vs工具来看看它把我带到了哪里。我将记录开发人员进行改进所需的实际时间以及运行所需的时间(相互对照)。
- @280Z28:很好的测试,你有权利不去听我告诉你问题实际上在哪里,所以你将依靠vs来告诉你。对不起,我希望你不要再沉迷于老式的代码了。
- @迈克:谢谢你的贝叶斯推理的例子。在表的最后一列中,P(f >= x)是正确的还是真的是P(f >= x | o=2/2)?
- @~unutbu:嗯,是的,我想。它只是归一化为1的倒数第二列。
- @迈克:多年来,Shark(包括OSX devtools免费提供)一直在做你所要求的事情,加上许多其他事情(而且没有任何调试开销)。
- @斯蒂芬:我在窗户上。我在谷歌上搜索过鲨鱼。我得到它的样本堆栈。即使在IO等待期间,我也无法判断它是否采集样本。我不知道它是否在调用指令中告诉您包含该指令的样本的百分比。我没有看到任何建议您可以实际浏览单个堆栈示例。所以它看起来像变焦(如果它在IO期间采样的话)。然后是附加状态上下文的问题,这两个都没有给你。(在我看来,"很多其他事情"是分散注意力的。)
- @Mike:Shark还可以在IO等待、系统调用和其他线程状态期间进行抽样,并提供从分析中包含或排除这些数据点的方法。它将示例属性化为函数和特定的指令,并允许您应用不同的过滤方法来精确地查看在哪里花费了多少执行时间,它还允许您浏览示例。这是一个非常强大的工具,非常容易使用,但很少有人会费心刮擦下面的表面。
- @斯蒂芬:好吧,我印象深刻。我假设"花费时间的百分比"实际上意味着堆栈中样本的百分比(因为很多人对此有点模糊)。仅栈是相当好的,但通常状态上下文在证明操作不必要时会产生至关重要的差异。
- 这正是GoogleCupprofiler的工作原理——它对整个调用堆栈进行采样。
- @克里姆金:如果你知道该怎么说,那就对了。即使他们说教授更好,还是更喜欢真实的。如果你知道要问的话,它会给出行级别百分比。文本输出有6列-忽略1到4。图形输出-在真实的软件中,是一个老鼠窝,里面充满了与之无关的节点,因为它们不是你的,会被递归搞得一团糟。&100赫兹的样本可能是致命的。
- @ 280Z28:到目前为止,你可能已经忘记了这一点,但是SooCoFig上有一个新的代码示例(C++)。它有一系列表示加速迭代的代码库,以及示例记录,还有一个简短的PowerPoint,您可以在其中浏览。
- 你必须相信你的内核,它会随机地抢占你的程序。如果您有类似于自愿内核抢占(哪一个是?)一些发行版的默认值),那么您就不能再信任结果了。自愿性内核抢占也无助于调试多线程程序(有人告诉我一个玩具代码,它通常会在一秒钟内死锁,完全内核抢占可以运行3小时,自愿性内核抢占)
- @batchyx:我唯一一次看到这个问题是在w3.1上的vc中,如果你点击"暂停",它就不会停止,直到下一个windows消息!我们不是在吹毛求疵。如果我的邻居看到我的狗5天中有3天在他的院子里放松,问我这件事,我有理由说"这并不意味着什么——你只有5个样本,而且它们甚至不是随机的"?在使方法无效之前,非随机性必须非常糟糕。
- @Mikedunlavey:使用volontary内核(而不是用户空间,它仍然是完全抢占的)抢占,程序在进行某些系统调用(例如,write)时更可能被抢占,因此您的程序在随机write()上(如登录到标准输出)比在实际计算上更可能被抢占。
- @batchyx:问题是:当你点击^C时,程序正在做什么?如果是做计算,你需要知道。如果是做I/O,你需要知道。这些都是运行挂钟的同样重要的方法,也是你可能试图避免做的同样的事情。如果它被另一个进程抢先,很好——中断仍然是一个无偏的样本。即使线程以正常速度的1%运行,由于竞争,您看到的是百分比,这不会受到太大的影响。
- @Mikedunlavey:"当你点击^C键的时候",有点太简单了。你必须穿过几层,最终形成一个可以完成kill()的东西。但如果执行kill(),则必须运行杀戮过程。在一个单核系统上,如果这个杀戮过程运行,那么你想要杀戮的程序就不会运行;它以前已经被预先处理过了。如果抢占更具有确定性,那么它就不那么随机了。
- @batchyx:好的,你说的是线程A可能是CPU绑定的,阻止线程B发出终止命令,所以线程B只能在A自愿进入等待状态时发出终止命令,人为地将中断集中到等待状态。但是,我希望从键盘到杀戮的路径发生在抢先级别,这样它就不会被阻塞。如果不是的话,你就不能杀死一个无限循环。
- @Mikedunlavey:内核中没有这样的无限循环,并且CPU绑定的用户空间进程(从不执行任何系统调用(进行volontary抢占))在一段时间后仍然被抢占。
- 我称这种技术为"随机分析"。很好的工具,当你想知道从哪里开始。
- @DJ史密斯:随便说吧,但不要低估了。查看此处的PDF幻灯片,其中发现和删除了多个"瓶颈",总计超过原始时间的99.8%。代码就在那里。我还没有看到有人使用分析器工具获得类似的加速。
- @Mikedunlavey:你可能想看看dtrace.org/blogs/brendan/2011/12/16/flame-graphs。似乎是一个很好的形象化你的意思的方式。现在,如果我能找到一些方法用它来想象一个callgrind垃圾场…
- @利奥:它看起来很性感,但是如果有一个函数foo,它只出现在垂直拍摄的中间,它不会吸引你的注意力。哦,如果你只取5个、10个或20个样本,打印出来,用你的眼球看着它们,你会看到它们上面的foo,比例大致相当于你可以节省的,如果你可以让它不花时间,你会看到为什么叫它。可能不需要经常打电话。这就是我一直想通过的。
- @Mikedunlavey:不能再同意了,我觉得你的方法似乎太简单了,直到我尝试了,实际上我想我得到了什么!不过,这是我第一次看到有人尝试这样的可视化。
- 这是一个非常好的方法来优化手边的代码,但如果我只是想了解如何重写多核多GPU系统的所有内容,那该怎么办?我用gprof2dot尝试了gprof、callgrind、oprofile和perf调用图,但火焰图似乎是最直观的,因为它们查看整个堆栈。我想我们最终会为"vtune"买单,因为其他人似乎对调用图以外的任何东西都没有那么好。
- @Dashesy:不管是不是多核GPU,如果线程速度增加一倍,那么整个过程的速度就会增加一倍。你得把线从单线程中去掉,所以一次集中在一个线程上。不要对那些工具大惊小怪。如果你能在20%到80%的范围内,对那根线做些什么来剃掉,那么会有少量的样本找到它,你会从25%到400%加速。再重复一遍,还有很多你以前没见过的。这就是你从萝卜中获取血液的方法。
- 无耻插头:github.com/muon/gdbprof
- @电工:这么近,这么远。首先,什么是好的:在墙上的时钟时间获取堆栈样本。那么什么不是(PMP有相同的问题):你如何处理这些样本。您假设您需要一个大的数字,因此您假设您需要将它们折叠起来并对它们进行计数(您也会丢失行号信息)。相反,用户应该看一个并理解它。然后再看另一个。然后另一个。马克斯10岁或20岁。这种耗时的模式将是显而易见的,而且它可能不会采用重复多次的整个堆栈样本的形式。试试看。
- @Electro:例如,在您显示的堆栈跟踪中,您的例程(可能唯一能够修复的例程)可能是poll, xcb_wait_for_reply, XReply, intel_update_renderbuffers, intel_prepare_render。其中每一个都在2/3的时间内处于活动状态,所以很有可能只有3个样本会将它们识别为您应该查看的例程。
- @Mikedunlavey:我认为它更像是一个补充工具,在看到一些样本后确认我的怀疑。行信息确实很重要-我在调试X视频驱动程序中的神秘常数memcpy()时需要它,但是非常的声明需要非常(大量)的证据,所以我在飞行中编造了类似的东西。另外,在切换终端窗口停止渲染循环时很有用,因此我甚至无法获取这些示例,否则:p我将很快向工具添加更多信息。
- 不幸的是,"用你的眼球"部分没有缩放。对于重多线程程序,您需要额外的帮助。
- 再次嗨@thorbj&248;rn:)希望你做得很好。如果你已经尝试过了,有一个真正的问题需要解决,并且在一个积极的心态下,我们可以讨论它的利弊。我对多线程应用程序的唯一体验是有大量的跨线程异步协议。在这种情况下,我的经验是随机停顿只会让我走到一条路。为了解决剩下的问题,我必须恢复日志记录技术。
- nf+/-sqrt(nf(1-f))——这是一个众所周知的公式还是规则?它叫什么,所以我可以用谷歌搜索。
- @用户986697:这是二项式分布。如果每次取样时碰到问题的概率是p,那么如果你取n个样本,碰到问题的平均数是n p。方差是np(1-p),标准差是它的平方根。更多统计信息。
- 我可能有点慢,迈克,但是我现在已经在几个答案上阅读了你对这个方法的解释,我仍然有点不确定如何将它应用到我的问题领域。我们有一个SOAP Web服务,它使用太长的时间(1-2sec)来计算返回的答案。我该如何找到关注点?根据我所读到的,这就是我要做的:1.用Web服务启动我的IDE。2。创建一个针对我的开发人员框3的请求循环。定期暂停调试器,注意我们在4时暂停的行和源文件。汇编一份按5的行的统计。???现在呢?
- 如果程序似乎大部分时间都在某个库函数中使用,从不同的地方调用,该怎么办?P.S.喜欢这个答案讨论在四年后仍然活跃的事实!
- @oligofren:稍微加点盐,因为我对web应用程序没有经验,只有少量线程,有时还有异步协议(哪些是web应用程序)。最基本的问题是,为什么这一刻会被花费?忘记统计数据-把每个样本当作要发现的错误来看待。当你看到它做了两次你可以找到一种方法避免,修复它,并得到一个加速。异步协议的问题是,很难立即停止所有操作,这样您就可以知道为什么要花费这一刻了。然后我使用了一种费力的测井方法。
- @利奥弗伦:有时人们会想,"但这不会错过真正的问题吗?"别担心。无论"真正的问题"是什么,它都会找到它,如果你不是第一次尝试它,那么第二次或第三次,因为每加快一次,你就会把其他问题更多地抛到救济中。
- @寡聚体:在第3步。你只需要记下行和源文件。正如迈克在回答中解释的那样,您应该注意整个堆栈跟踪。
- 我该如何感谢你的回答?几个星期以来,我一直在努力寻找复杂设置(MPI->A自定义运行时->Threading等)的问题。我刚刚编写了一个简单的脚本,在其中一个节点上反复调用gdb(在我的例子中,这与哪一个节点无关),显示线程,并在其中一个线程的随机转储回跟踪中进行调用。反复调用这个脚本,我终于知道问题出在哪里了!我得到了很大的改进,结果发现这个问题是根本性的,但也非常容易解决。我爱你,迈克·邓拉维!:)
- (见我之前的评论。)对于那些不熟悉gdb的人,你所要做的就是"gdb-batch-x命令./executable pid",其中命令包含(在我的例子中)第一行的"info threads",第二行的"bt"用于回溯,最后一行的"q"用于退出,这可能是不必要的。我只是运行一个大作业,登录到分布式机器上的一个节点,然后运行这个简单的脚本。我注意到这个问题是由我的自定义运行时做出的假设导致的,这些假设对我的应用程序代码不起作用。剩下的只是一线定局和胜利!:)
- @Willhains:有时它会落在您无法更改的代码中,比如字符串比较、内存分配或I/O,但您同样可能会看到更高级别的问题,所以您可以避免这样做。大型软件可以有几十层深的堆栈。这意味着你几乎肯定会看到一些不需要做的事情,所以你可以得到很好的加速。
- 好主意!谢谢!我试图通过大量使用模板来描述一个数学库,所以这个概要文件(Xcode的工具)并没有给我足够的昂贵部件的位置。这个方法真的有用!
- 在阅读了@ MikeDunlavey的答案后,任何人都可以参考并解释一个合适的工具,它可以用于为一个图像处理应用程序编写的一个大型C++代码的调用采样。应高度赞赏使用该工具的链接和教程。我特别想找出可以使用CUDA并行化的代码段,以及主要与计算密集型任务相关的瓶颈,这些任务应该是移植CUDA内核的合适人选。
- 你好,穆尼什。我最喜欢的工具是任何一个调试器,当它暂停时,可以显示每个线程的当前状态。他们中的大多数人只是在等待工作,所以他们不重要。在有关系的线程上,它们可以向我显示所有代码和数据,这样我就可以真正地知道为什么要花费时间。还有其他工具,如pstack或lsstack。就我个人而言,我不会分享人们对工具的渴望,除非他们真的做了我需要做的事情。这段时间不是用来采集样本的。时间花在检查和思考每一个问题上。
- 非常感谢@mikedunlavey的澄清。我将很快在这个论坛上使用您概述的技术更新我的经验。
- 愚蠢的问题:这不正是gprof的工作方式吗?en.wikipedia.org/wiki/gprof网站
- @Florianrichoux:a)I/O可以解释很多时间。gprof忽略了它。b)gprof不对堆栈进行采样,因此无法定位耗时的函数调用。c)它体现了一些神话,例如1)计时的精确性比确定有罪代码更重要,2)递归是一件大事,等等。这里有一个更长的列表。与许多分析人员共享的总体结果是,如果发现了一个小问题,程序员会修复它并祝贺她/自己,同时忽略了一些大问题。
- 嗨,迈克,谢谢你的回答。但是,你能不能添加一些实际的例子,在这些例子中,测量并不能给出完整的图像?我知道为什么叠加样本高度精确,但是测量忽略了什么?而且,这个图表也不是很清楚。什么是水平,什么是垂直,什么是云?
- @Muditjain:我假设你检查了我上面链接的更长的列表,特别是第5点,以及链接到示例的帖子。试着给出一个更一般的画面,如果懒惰的虫子是敌人,你必须打败所有的虫子,但是它们只需要打败你一次,所以如果有一只虫子藏在你面前,你就不会得到你所能得到的所有加速。试图精确测量的分析人员会放弃其他信息。因此,如果例程"foo"在堆栈57.34%的时间内,那么这就不能说明是什么调用链导致了它,也不能说明在该时间内它和调用链中的哪些语句在它下面。…
- @穆迪特贾恩:…如果你不知道呼叫链会导致什么,你就无法判断它是否可能不需要像现在这样被呼叫。如果您不知道函数中的哪些语句占了大部分时间,那么您就只能猜测如何使其更快。如果你知道哪条语句解释了时间,但不知道它的调用,你就只能猜测如果下面有什么可以修复的话。最好是对调用堆栈进行人工查看,因为任何代价为x%的问题平均都会出现在堆栈的x%上。它不能对你隐瞒。…
- @穆迪特贾恩:…但是,作为人类,您只能检查大约20个堆栈样本。这意味着,平均而言,速度缺陷必须大于10%。许多明显的bug可能比这个小,但幸运的是,有一些更大的bug试图隐藏,但无法对你隐藏。当你修复它们时,它们会使较小的更大,因此当你重复这个过程时更容易找到。测量的问题是,通过成为目标,一些速度错误可以对你隐藏。当这种情况发生时,程序看起来是最佳的,但事实并非如此。
- @Mikedunlavey:谢谢你的回答,最后一个问题很抱歉。你所说的"成为目标"是什么意思?衡量的问题是,通过成为目标,一些速度错误可以对你隐藏?
- @Muditjain:我的意思是:任何可以被删除的活动,如果消耗了大量的时间,都是一个速度缺陷,或者说更好,都是一个通过删除或减少速度来获得额外速度的机会。这些机会中的一些(大部分)并不表现为您可以衡量的东西。他们躲开了你的工具。有几个速度错误(通常)。当你移除了你能找到的,你就只剩下那些你找不到的,它们限制了你的速度。这就是为什么你需要一种强调发现而非测量的方法。
- 这个答案正在meta上讨论。
- @彼得:我很确定这是对的,但我想我最好再检查一下(从现在起几个小时内)。谢谢。
您可以在以下选项中使用valgrind
1
| valgrind --tool=callgrind ./(Your binary) |
它将生成一个名为callgrind.out.x的文件。然后,可以使用kcachegrind工具读取此文件。它会给你一个图形化的分析结果,比如哪条线的成本是多少。
- Valgrind很好,但是要注意它会让你的程序变慢。
- 还可以查看gprof2dot以获得一种令人惊奇的可视化输出的替代方法。./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
- @Neves是的,Valgrind对于实时分析"gstreamer"和"opencv"应用程序的速度并不是很有帮助。
- stackoverflow.com/questions/375913/…是速度问题的部分解决方案。
- @塞巴斯蒂安:gprof2dot现在在这里:github.com/jrfonseca/gprof2dot
我猜你在使用GCC。标准的解决方案是使用gprof进行分析。
在分析之前,一定要将-pg添加到编译中:
1
| cc -o myprog myprog.c utils.c -g -pg |
我还没试过,但我听说过关于谷歌性能工具的好消息。这绝对值得一试。
相关问题。
如果gprof不适合你,还有其他一些流行词:Valgrind、Intel vtune、Sun dtrace。
- 我同意gprof是当前的标准。不过,需要注意的是,valgrind用于分析内存泄漏和程序的其他内存相关方面,而不是用于速度优化。
- 比尔,在流浪者套房里,你可以找到卡林林和马西夫。这两个都对配置应用程序非常有用
- @比尔·利扎德:关于gprof的一些评论:stackoverflow.com/questions/1777556/gprof的替代品/…
- 另请参见下面我的gprof警告,stackoverflow.com/a/6540100/823636
- pg只是调用堆栈分析的一个近似值。它插入mcount调用来跟踪哪些函数正在调用哪些其他函数。它使用基于时间的标准采样,呃,时间。然后,它将函数foo()中采样的时间分配回foo()的调用方,根据调用的数目进行分配。所以它不能区分不同成本的电话。
较新的内核(例如最新的Ubuntu内核)附带了新的"性能"工具(apt-get install linux-tools即性能事件)。
这些配备了经典的采样配置文件(手册页)以及出色的时间表!
重要的是,这些工具可以是系统分析,而不仅仅是进程分析——它们可以显示线程、进程和内核之间的交互,并让您了解进程之间的调度和I/O依赖性。
- 伟大的工具!我是否可以从"main->func1->fun2"样式中得到一个典型的"butterfly"视图?我好像不明白…perf report似乎给了我调用父函数的函数名…(所以这是一个倒蝴蝶的视图)
- 将,性能是否可以显示线程活动的时间表;是否添加了CPU号信息?我想知道每个CPU上运行的线程是什么时候运行的。
- @kizzx2-你可以使用gprof2dot和perf script。非常好的工具!
- 甚至像4.13这样的较新内核也有用于分析的EBPF。参见brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html和brendangregg.com/ebpf.html。
- 另一个很好的关于perf的介绍出现在archive.li/9r927 selection-767.126-767.271中(为什么上帝决定从so知识库中删除该页面超出我的范围…)
- 这应该是公认的答案。使用调试器会在示例中引入太多的噪声。Linux的性能计数器适用于多线程、多进程、用户和内核空间,这非常好。您还可以检索许多有用的信息,如分支和缓存未命中。在andrewstern提到的同一个网站上,有一个火焰图对这种分析非常有用:火焰图。它会生成SVG文件,这些文件可以通过Web浏览器打开,用于交互式图形!
我将使用valgrind和callgrind作为分析工具套件的基础。重要的是要知道,Valgrind基本上是一个虚拟机:
(wikipedia) Valgrind is in essence a virtual
machine using just-in-time (JIT)
compilation techniques, including
dynamic recompilation. Nothing from
the original program ever gets run
directly on the host processor.
Instead, Valgrind first translates the
program into a temporary, simpler form
called Intermediate Representation
(IR), which is a processor-neutral,
SSA-based form. After the conversion,
a tool (see below) is free to do
whatever transformations it would like
on the IR, before Valgrind translates
the IR back into machine code and lets
the host processor run it.
callgrind是一个在此基础上构建的剖析器。主要的好处是,你不必运行你的申请数小时,以获得可靠的结果。即使是第二次运行也足以获得岩石坚实可靠的结果,因为CallGrind是一个非探测剖面仪。
另一个建立在Valgrind基础上的工具是Massif。我使用它来分析堆内存使用情况。效果很好。它所做的是为您提供内存使用情况的快照——详细的信息是什么占了内存的百分比,以及谁把它放在那里。这些信息在应用程序运行的不同时间点可用。
如果没有一些选择,运行valgrind --tool=callgrind的答案就不完全了。我们通常不想在Valgrind下描述10分钟缓慢的启动时间,而想在程序执行某些任务时描述它。
所以这就是我推荐的。先运行程序:
1
| valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp |
现在,当它工作并且我们想要开始分析时,我们应该在另一个窗口中运行:
这将打开分析。要关闭它并停止整个任务,我们可能会使用:
现在我们在当前目录中有一些名为callgrind.out.*的文件。要查看分析结果,请使用:
1
| kcachegrind callgrind.out.* |
我建议在下一个窗口中单击"self"列标题,否则它将显示"main()"是最耗时的任务。self"显示每个函数本身花费了多少时间,而不是与从属函数一起使用。
- 出于某种原因,callgrind.out.*文件总是空的。执行callgrind-control-d有助于将数据强制转储到磁盘。
- 我对这些说明感到困惑。你是说当程序运行时,我们可以在另一个窗口中执行callgrind_control来打开/关闭分析吗?在我看来,最好设计一个只包含你想要分析的内容,然后分析整个程序的最小程序。
- 不能。我通常的上下文是像整个mysql或php或者类似的大东西。一开始常常连我想分开的东西都不知道。
- 或者,在我的例子中,我的程序实际上将一组数据加载到一个LRU缓存中,我不想对此进行分析。因此,我在启动时强制加载缓存的一个子集,并仅使用该数据来分析代码(让OS+CPU管理缓存中的内存使用)。它可以工作,但是在我试图在不同的上下文中分析的代码中,加载缓存的速度很慢而且CPU很密集,所以CallGrind会产生严重污染的结果。
- 也有EDOCX1[1]以编程方式启用/禁用收集;请参阅stackoverflow.com/a/13700817/288875
这是对Nazgob的gprof答案的回应。
最近几天我一直在使用gprof,并且已经发现了三个显著的限制,其中一个是我在其他地方还没有看到记录的:
它不能在多线程代码上正常工作,除非使用解决方法
调用图会被函数指针混淆。示例:我有一个名为multithread()的函数,它允许我在指定的数组上多线程处理指定的函数(两者都作为参数传递)。然而,gprof将对multithread()的所有调用视为计算在儿童身上花费的时间的等价物。由于我传递给multithread()的某些函数比其他函数花费的时间更长,因此我的调用图基本上是无用的。(对于那些想知道线程是否是问题所在的人:不,multithread()可以选择,在这种情况下,只在调用线程上按顺序运行所有内容)。
上面写着"…呼叫数数字是通过计数而不是抽样得出的。它们完全准确……。然而,我发现我的调用图给了我5345859132+784984078作为我最被调用函数的调用统计数据,其中第一个数字应该是直接调用,第二个递归调用(都来自于它本身)。因为这意味着我有一个bug,所以我在代码中放入了长(64位)计数器,并再次运行相同的计数器。我的计数:5345859132直接调用和78094395406自递归调用。这里有很多数字,所以我要指出的是,我度量的递归调用是78BN,而gprof是784M:100个不同的系数。两个运行都是单线程和未经优化的代码,一个编译了-g,另一个编译了-pg。
这是GNU gprof(gnu-binutils for debian)2.18.0.20080103,在64位debian-lenny下运行,如果这对任何人都有帮助的话。
- 显然,它可以对stackoverflow.com/a/11143125/32453进行采样
- 是的,它进行抽样,但不计算通话次数。有趣的是,通过你的链接,我最终找到了我在文章中链接到的手册页面的更新版本,新的URL:sourceware.org/binutils/docs/gprof/…这重复了我答案第(i i i)部分中的引用,但也会说"在多线程应用程序中,或是与多线程库链接的单线程应用程序中,只有计数函数是线程安全的,UNT才具有确定性。(注意:注意glibc中的mcount计数函数不是线程安全的)。"
- 我不清楚这是否解释了我在(iii)中的结果。我的代码是链接的-lpthread-lm,声明为"pthread_t*thr"和"pthread_mutex_t nextlock=pthread_mutex_initializer"静态变量,即使它运行的是单线程的。我通常会假定"链接到多线程库"意味着实际使用这些库,并且在更大程度上超过了这一点,但我可能是错的!
使用valgrind、callgrind和kcachegrind:
1
| valgrind --tool=callgrind ./(Your binary) |
生成callgrind.out.x。使用kcachegrind读取。
使用gprof(添加-pg):
1
| cc -o myprog myprog.c utils.c -g -pg |
(不适合多线程、函数指针)
使用谷歌性能工具:
使用时间采样,可以发现I/O和CPU瓶颈。
英特尔vtune是最好的(免费用于教育目的)。
其他:amd codeanalyst(自被amd codexl取代以来),oprofile,"perf"工具(apt-get-install-linux工具)
对于单线程程序,可以使用igprof,不光彩的profiler:https://igprof.org/。
它是一个采样剖面仪,沿着…长…由MikeDunlavey回答,他将把结果包装在一个可浏览的调用堆栈树中,并用每个函数(无论是累积函数还是每个函数)花费的时间或内存进行注释。
以下是我用来加快代码速度的两种方法:
对于CPU绑定的应用程序:
在调试模式下使用探查器来识别代码中有问题的部分
然后切换到发布模式,注释掉代码中有问题的部分(不加任何注释就将其存根掉),直到您看到性能的变化。
对于I/O绑定的应用程序:
在发布模式下使用探查器来识别代码中有问题的部分。
N.B.
如果你没有一个轮廓仪,就用那个可怜人的轮廓仪。调试应用程序时按暂停。大多数开发人员套件将分解为带有注释的行号的程序集。从统计上讲,您很可能会降落在一个占用大部分CPU周期的区域。
对于CPU,在调试模式下进行分析的原因是,如果您尝试在发布模式下进行分析,编译器将减少数学、矢量化循环和内联函数,这些函数在组装时会使代码变得不可映射的混乱。不可映射的混乱意味着探查器将无法清楚地识别花费这么长时间的内容,因为程序集可能与优化下的源代码不对应。如果您需要释放模式的性能(例如时间敏感),请根据需要禁用调试器功能以保持可用性能。
对于I/O绑定,探查器仍然可以在释放模式下识别I/O操作,因为I/O操作要么外部链接到共享库(大多数时间),要么在最坏的情况下,将导致系统调用中断向量(也很容易被探查器识别)。
- +1可怜人的方法对于I/O绑定和CPU绑定的效果一样好,我建议在调试模式下进行所有性能调整。完成调谐后,打开释放。如果程序在您的代码中是CPU绑定的,那么它将得到改进。这是一个粗略但简短的过程视频。
- 我不会使用调试版本进行性能分析。我经常看到在调试模式下性能关键的部分在发布模式下被完全优化了。另一个问题是在调试代码中使用断言,这会给性能增加噪声。
- 你读过我的帖子吗?"如果您需要释放模式的性能(例如时间敏感),请根据需要禁用调试器功能以保持可用的性能,""然后切换到释放模式,并注释代码中有问题的部分(无需任何内容将其存根),直到您看到性能的变化为止。"?我说在调试模式下检查可能的问题区域,并在发布模式下验证这些问题,以避免您提到的陷阱。
同样值得一提的是
hpctoolkit(http://hpctoolkit.org/)—开放源码,适用于并行程序,并具有一个GUI,可通过多种方式查看结果。
英特尔vtune(https://software.intel.com/en-us/vtune)-如果您有英特尔编译器,这非常好
tau(http://www.cs.uoregon.edu/research/tau/home.php)
我使用了hpctoolkit和vtune,它们在查找帐篷中的长极点方面非常有效,不需要重新编译代码(除了必须使用-g-o或relwithdebinfo类型的内置cmake才能获得有意义的输出)。我听说陶在能力上是相似的。
可以使用IProf库:
网址:https://gitlab.com/neurochrom/iprof
网址:https://github.com/neurochrom/iprof
它是跨平台的,不允许您实时测量应用程序的性能。你甚至可以把它和一个实时图表结合起来。完全免责声明:我是作者。