关于UNIX:如何配置Linux上运行的C++代码?

How can I profile C++ code running on Linux?

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


如果您的目标是使用分析器,请使用建议的分析器之一。好的。

但是,如果您很着急,并且可以在调试器主观性缓慢的情况下手动中断程序,那么有一种简单的方法可以发现性能问题。好的。

只需暂停几次,每次查看调用堆栈。如果有一些代码浪费了一定比例的时间,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。这给了我们对If频率的新估计,根据此:好的。

    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=10f=0.3,即3+/-1.4样本。)好的。

    此外,为了直观地感受测量和随机叠加采样之间的差异:现在有一些轮廓仪可以对堆栈进行采样,即使是在墙上的时钟时间,但得到的结果是测量值(或者热路径,或者热点,从中可以很容易地隐藏"瓶颈")。他们没有给你展示的(他们很容易)是实际的样品本身。如果你的目标是找到瓶颈,你需要看到的平均数是2除以所需时间的分数。因此,如果需要30%的时间,平均2/.3=6.7个样本会显示出来,20个样本显示出来的概率是99.2%。好的。

    这是一个现成的说明检查测量和检查堆叠样品之间的差异。瓶颈可能是这样的一个大斑点,也可能是许多小斑点,这没有区别。好的。

    enter image description here好的。

    测量是水平的,它告诉你特定于时间的例行程序需要多少时间。取样是垂直的。如果有任何方法可以避免整个程序此时正在做的事情,并且如果在第二个示例中看到它,那么就发现了瓶颈。这就是造成差异的原因——看到花费时间的全部原因,而不仅仅是多少。好的。好啊。


    您可以在以下选项中使用valgrind

    1
    valgrind --tool=callgrind ./(Your binary)

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


    我猜你在使用GCC。标准的解决方案是使用gprof进行分析。

    在分析之前,一定要将-pg添加到编译中:

    1
    cc -o myprog myprog.c utils.c -g -pg

    我还没试过,但我听说过关于谷歌性能工具的好消息。这绝对值得一试。

    相关问题。

    如果gprof不适合你,还有其他一些流行词:Valgrind、Intel vtune、Sun dtrace。


    较新的内核(例如最新的Ubuntu内核)附带了新的"性能"工具(apt-get install linux-tools即性能事件)。

    这些配备了经典的采样配置文件(手册页)以及出色的时间表!

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

    Alt text


    我将使用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

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

    1
    callgrind_control -i on

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

    1
    callgrind_control -k

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

    1
    kcachegrind callgrind.out.*

    我建议在下一个窗口中单击"self"列标题,否则它将显示"main()"是最耗时的任务。self"显示每个函数本身花费了多少时间,而不是与从属函数一起使用。


    这是对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下运行,如果这对任何人都有帮助的话。


    使用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操作要么外部链接到共享库(大多数时间),要么在最坏的情况下,将导致系统调用中断向量(也很容易被探查器识别)。


    同样值得一提的是

  • 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

    它是跨平台的,不允许您实时测量应用程序的性能。你甚至可以把它和一个实时图表结合起来。完全免责声明:我是作者。