C++ 分析和优化

Posted

技术标签:

【中文标题】C++ 分析和优化【英文标题】:C++ profiling and optimization 【发布时间】:2013-02-25 10:28:04 【问题描述】:

我的应用程序性能存在一些问题。我在 *** 上找到了这个答案: https://***.com/a/378024/5363

我喜欢。我不太明白的一点是代码优化和分析之间的关系。因为显然人们想要分析优化的代码,但同时在优化过程中会丢失很多信息。那么在调试器中运行优化代码并按照引用的答案中的建议闯入它是否可行?

我在 Linux 下使用带有 gcc 的 CMake,如果这有什么不同的话。

【问题讨论】:

先配置,后优化。分析的作用是帮助您识别“热点”,您可以在其中进行优化。而且您所做的优化是在您编写的实际代码中进行的,因此调试器或进一步分析没有问题。 @JoachimPileborg,但是我怎么知道瓶颈不是由假设代码将被优化而编写的库引起的?例如。根据我之前的经验,STL 在 Debug 版本中的表现非常糟糕。在这种特殊情况下,瓶颈似乎是一些矩阵运算(使用 Eigen 库) @Grzenio 不要分析调试版本! 您只需要调试信息,而不是编译调试版本。您应该启用优化配置文件。 如果适用,首先使用time 运行您的程序。如果您的程序大部分时间都花在系统调用或等待 I/O 完成上,那么用户/系统/真实的比率应该可以说明。 【参考方案1】:

一般定律称为帕累托定律,the law of 80/20:

20% 的原因会产生 80% 的后果。

通过分析,您将确定导致应用程序变慢/消耗内存或其他后果的 20% 的最重要原因。如果你解决了 20% 的原因,你将解决 80% 的缓慢/内存消耗等问题......

当然,这些数字只是数字。只是为了给你它的精神:

您必须只关注真正的主要原因,以便改进优化,直到您满意为止。

从技术上讲,使用 linux 下的 gcc,简而言之,对您提到的“How can I profile C++ code running in Linux?”问题的回答建议使用:

gprof。 google-perftools Valgrind Intel VTune Sun DTrace

【讨论】:

也称为“最低挂果”,即以最小的努力获得最大的回报。 我同意 100%!不过,我该如何从技术角度做到这一点? @PeterWood thx,我没搞懂那个,比喻真好:-)【参考方案2】:

如果您需要收集堆栈样本,为什么要通过调试器来完成。定期运行 pstack。您可以将每次运行的输出重定向到不同的文件,然后再分析这些文件。通过查看这些文件的调用堆栈,您可能会找出热函数。您不需要调试二进制文件,并且可以在完全优化的二进制文件上执行上述操作。

我更喜欢使用分析器工具来执行上述操作或执行您引用的线程中列出的操作。他们快速查明最热门的函数,您可以通过查看调用者被调用者图来了解调用堆栈。我会花时间了解调用者被调用者堆栈,而不是使用上述方法分析随机堆栈。

【讨论】:

【参考方案3】:

正如 Schumi 所说,您可以使用 pstack 之类的东西来获取堆栈样本。 但是,您真正需要知道的是为什么程序会花费采样时间。 也许您可以仅从一堆函数名称中弄清楚这一点。 如果您还可以看到发生调用的代码行,那就更好了。 如果您可以看到参数值和数据上下文,那就更好了。 原因是,与您正在寻找“热点”、“慢方法”、“瓶颈”的流行概念相反——即基于测量的观点,要寻找的最有价值的事情是正在做的事情可以被淘汰

换句话说,当您在调试器中停止程序时,把它正在做的任何事情都当成一个错误来考虑。 尝试找到一种方法不做那件事。 然而,在你取另一个样本并看到它在做同样的事情之前,不要这样做——不管你如何描述那件事。 现在您知道这需要大量时间。 多少时间?没关系 - 修复后您会发现。 你知道这是很多。在看到它之前必须采集的样本越少,它就越大。

然后是“放大效果”。在你修复了那个“速度错误”之后,程序将花费更少的时间——但是——这不是唯一的。 还有其他的,现在他们花费了更多的时间。 所以再做一遍。 当你完成这个时,如果程序比玩具还大,你可能会惊讶于它的速度有多快。 Here's a 43x speedup. Here's a 730x speedup. Here's the dreary math behind it.

您知道,工具的问题在于您为采样的便捷性付出了代价。 由于您将其视为衡量标准,因此您并没有专注于代码执行其正在执行的操作的原因 - 可疑 原因。 这会导致您错过使代码更快的机会, 让你错过放大效果, 导致您在最终可能的加速之前停下来。

编辑:为火焰道歉。现在回答你的问题 - 我直到最后才打开编译器优化,因为它可以掩盖更大的问题。 然后我尝试做一个打开了优化但仍然有符号信息的构建,这样调试器就可以获得合理的堆栈跟踪并检查变量。 当我遇到加速回报递减时,我可以通过测量总体时间来了解优化器产生了多大的差异 - 不需要分析器。

【讨论】:

感谢您的回答!如何让 gcc 将符号信息保留在优化的构建中?在调试版本中,我发现 Eigen(线性代数包)似乎需要永远乘以小向量和矩阵([2x1]*[2x2][1x2]),但这应该几乎是立即的。这就是为什么我想看看优化构建是否有帮助。 @Grzenio: 1) 抱歉,我很少使用 gcc 及其链接器这样做,但如果您花几个小时浏览它的选项,应该可以。 2)对于乘法矩阵,特别是如果它们很小,我会通过对它们进行数以百万计并抓取堆栈来进行测试。我对 LAPACK 中发生的事情感到惊讶(主要是检查参数)。我敢打赌,那里的任何优化都隐含地假设了大矩阵。对于小型矩阵,您几乎可以肯定使用手动编码的例程运行得更快。任何库的存在都是为了让您更容易编写,并假设您可以容忍一些缓慢。 @Grzenio:例如,我不会为编写像void MxM2(double a[], double b[], double c[]) 这样的例程而感到自豪,这意味着 C = A*B 其中 A、B 和 C 是 2x2 矩阵,并且只是展开所有代码在那里。这对于编译器来说很难被击败,您甚至可以将其设为宏或内联它。 @Grzenio:很抱歉继续。当你说乘法“似乎永远需要”和“应该几乎立即”时,我假设你已经意识到无论你做得多快,它仍然需要 100% 的时间。当您将执行时间从一分钟减少到一毫秒并且不能再进一步时,如果程序仍然花费 99% 的时间与矩阵相乘,那没关系。你知道你已经清除了所有的垃圾。

以上是关于C++ 分析和优化的主要内容,如果未能解决你的问题,请参考以下文章

C++应用程序性能优化——C++常用数据结构性能分析

(七) 下篇 Android 性能优化 Perfetto 详细介绍

C++ 编译器优化 - 为啥需要 constexpr?

优化C++软件——目录

优化C++软件——目录

C++实战之OpenCL矩阵相乘优化