C++性能分析和优化

3
我对我的应用程序性能有些问题。我在Stackoverflow上找到了这个答案: https://dev59.com/n3RC5IYBdhLWcg3wOOL1#378024
我很喜欢其中的一个部分,但是我不太理解代码优化和剖析之间的关系。因为显然人们想要对优化后的代码进行剖析,但同时在优化过程中会丢失很多信息。所以像引用中建议的那样,在调试器中运行优化后的代码并中断它是否切实可行呢?
如果这有区别,我正在使用带有gcc的Linux下的CMake。

先进行剖析,再进行优化。剖析的作用是帮助您识别“热点”,从而可以深入优化。而您所做的优化就在您编写的实际代码中,因此不会出现调试器或进一步剖析的问题。 - Some programmer dude
@JoachimPileborg,但是我怎么知道瓶颈不是由于一个假设代码将被优化的库引起的呢?例如,根据我的早期经验,STL在Debug构建中表现非常糟糕。在这种特殊情况下,瓶颈似乎是一些矩阵操作(使用Eigen库)。 - Grzenio
@Grzenio 不要对调试版本进行性能分析! - Peter Wood
你只需要调试信息,而不是编译调试版本。你应该在启用优化的情况下进行性能分析。 - JasonD
1
如果适用,先使用 time 运行您的程序。用户/系统/实际时间比率应该能够表明您的程序是否大部分时间都在系统调用或等待 I/O 完成。 - Aki Suihkonen
3个回答

5
一般法则被称为巴雷托法则,即80/20定律
- 20%的原因导致了80%的结果。
通过分析,您将确定导致应用程序变慢/消耗内存或其他后果的最重要的20%原因。如果您修复20%的原因,您将解决80%的缓慢/内存消耗等问题...
当然,这些数字只是数字。只是为了让您了解:
- 您必须只关注真正的主要原因,以改善优化直到满意。
在技术上,使用Linux下的gcc回答您所提到的“如何在Linux中运行C ++代码进行分析?”问题,建议简单地使用:

1
也被称为“最低的果实”,即用最小的努力获得最大的回报。 - Peter Wood
我完全同意!但从技术角度来看,我该怎么做呢? - Grzenio
@PeterWood 谢谢,我不知道那个,这个比喻真的很好 :-) - Stephane Rolland

1
如果您需要收集堆栈样本,为什么不通过调试器来完成呢?定期运行pstack即可。您可以将输出重定向到每次运行的不同文件中,并稍后分析这些文件。通过查看这些文件的调用堆栈,您可能会找出热点函数。您不需要调试二进制文件,可以在完全优化的二进制文件上执行上述操作。
我更喜欢使用分析工具来完成上述操作或执行您所提到的线程中列出的操作。它们可以快速确定热点函数,并且您可以通过查看调用者被调用者图形了解调用堆栈。我会花时间了解调用者被调用者堆栈,而不是使用上述方法分析随机堆栈。

1
正如Schumi所说,您可以使用类似pstack的工具来获取堆栈样本。然而,您真正需要知道的是程序在取样时花费时间的原因。也许您可以从函数名称的堆栈中推断出来。如果您还能看到调用发生的代码行,则更好。如果您还可以看到参数值和数据上下文,则更好。原因是,与测量为基础的观点相反,您要寻找的最有价值的东西是可以消除的事情。换句话说,当您在调试器中停止程序时,请将其正在执行的任何操作视为错误。尝试找到不执行该操作的方法。但是,在您采取另一个样本并看到它执行同样的操作之前,请勿这样做。现在您知道它需要很长时间。需要多少时间?无关紧要-在您修复它之后会发现。您确实知道它很大。在看到两次之前采取的样本越少,它就越大。
然后还有一个“放大效应”。在你修复了那个“速度漏洞”之后,程序将花费更少的时间——但是——那不是唯一的问题。还有其他问题,现在它们占用了更多的时间。所以再做一遍。当你完成这个任务时,如果程序比玩具大得多,你可能会惊讶于它变得更快了多少。 这里有一个43倍的加速。 这里有一个730倍的加速。 这是背后枯燥的数学原理。 你看,工具的问题在于,你为那种便利性付出了代价。由于你把它看作是测量,而没有专注于代码执行的原因-可疑的原因。这导致你错过了使代码更快的机会,错过了放大效应,使你远未达到最终可能的加速效果。

编辑:对于激动的回复感到抱歉。现在来回答你的问题 - 我不会在最后阶段之前打开编译器优化,因为它可能掩盖更大的问题。 然后我尝试进行启用了优化但仍具有符号信息以便调试器可以获得合理的堆栈跟踪和检查变量的构建。 当我遇到递减的加速收益时,我可以通过测量总时间来看到优化器带来了多少差异 - 不需要分析器。


谢谢您的回答!我该如何让gcc在优化构建中保留符号信息?在调试构建中,我发现Eigen(线性代数包)似乎需要很长时间才能将小向量和矩阵相乘([2x1]* [2x2] [1x2]),但这应该是几乎立即完成的。这就是为什么我想看看优化构建是否有所帮助。 - Grzenio
1
@Grzenio:1)很抱歉,我很少使用gcc及其链接器进行此操作,但如果您花几个小时浏览其选项,则应该是可能的。2)对于矩阵相乘,特别是它们很小的时候,我会通过执行大量计算并捕获堆栈快照来测试它。我已经对LAPACK中正在发生的事情感到惊讶(主要是检查参数)。我打赌其中的任何优化都隐含地假定了大矩阵。对于小矩阵,您几乎肯定可以通过手写程序运行得更快。存在任何库可让您轻松编写,并假定您可以容忍一些缓慢。 - Mike Dunlavey
例如,我并不介意编写像void MxM2(double a[], double b[], double c[])这样的例程,其中C = A * B,其中A、B和C是2x2矩阵,并展开其中的所有代码。这对于编译器来说很难超越,甚至可以将其制作成宏或内联。 - Mike Dunlavey
1
@Grzenio:抱歉一直在纠结这个问题。当你说乘法“似乎需要很长时间”并且“应该几乎是立即完成”的时候,我想你应该知道无论你多么快地进行计算,它仍然需要100%的时间。当你将执行时间从一分钟缩短到毫秒,并且不能再进一步优化时,如果程序仍然花费99%的时间在矩阵乘法上,那没关系。你知道你已经消除了所有浪费。 - Mike Dunlavey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接