代码之家  ›  专栏  ›  技术社区  ›  Gabriel Isenberg

如何配置Linux上运行的C++代码?

  •  1601
  • Gabriel Isenberg  · 技术社区  · 16 年前

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

    11 回复  |  直到 5 年前
        1
  •  1523
  •   Mike Dunlavey    4 年前

    如果您的目标是使用探查器,请使用建议的探查器之一。

    但是,如果您很匆忙,并且可以在调试器下手动中断程序,而程序主观上运行缓慢,那么有一种简单的方法可以发现性能问题。

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

    您可能有多个不同大小的性能问题。如果你清除了其中的任何一个,剩下的将占据更大的百分比,并更容易发现,在随后的通行证。这 放大效应 当多个问题复合在一起时,可能会导致真正巨大的加速因素。

    警告 :程序员倾向于怀疑这种技术,除非他们自己使用过。他们会说分析器提供了这些信息,但只有当他们对整个调用堆栈进行采样,然后让您检查一组随机样本时,这才是真的。(总结是失去洞察力的地方。)调用图不会提供相同的信息,因为

    1. 他们没有在指导级别进行总结,并且
    2. 他们在递归的情况下给出了令人困惑的摘要。

    一旦 . 如果您在多个样本上看到问题,那么它是真实的。

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

    P.P.S 一般来说,软件中的抽象层越多,就越有可能发现这是性能问题的原因(以及加速的机会)。

    :这可能不明显,但堆栈采样技术在存在递归的情况下同样有效。原因是,删除指令所节省的时间近似于包含该指令的样本的分数,而不管它在样本中出现的次数。

    我经常听到的另一个反对意见是: 它将在某个地方随机停止,并将错过真正的问题 ". 这来自于对真正的问题有一个先验的概念。 性能问题的一个关键特性是它们不符合预期。 抽样告诉你有些东西是个问题,你的第一反应是不相信。 这是很自然的,但你可以肯定,如果它发现了一个问题,它是真实的,反之亦然。

    补充 I f 时间的一部分(因此花费了那么多)。为了简单起见,假设我们不知道 是,但假设它是0.1,0.2,0.3。。。0.9,1.0,这些可能性的先验概率都是0.1,所以所有这些成本在先验上都是相同的。

    然后假设我们只取2个堆栈样本,我们看到指令 对两个样本进行指定观察 o=2/2 . 这给了我们新的频率估计 F 属于 ,据此:

    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)。换句话说,我们先前的确定性是

    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允许我们更新我们对可能成本的估计 . 如果数据量很小,它不会准确地告诉我们成本是多少,只是它足够大,值得修复。

    另一种看待它的方式叫做 Rule Of Succession . 如果你把一枚硬币掷两次,两次都是正面朝上,那么这能告诉你硬币可能的重量是多少? 最受尊重的回答是,这是一个Beta分布,具有平均值 (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75% .

    (关键是我们看到 &燃气轮机;0.)

    所以,即使是非常少的样本也能告诉我们它所看到的指令的成本。(平均而言,它将看到它们的频率与成本成比例。如果 n 采集样本,以及 F 将出现在 nf+/-sqrt(nf(1-f)) 样品。实例 n=10 , f=0.3 就是 3+/-1.4 样本。)


    补充
    现在有分析器可以对堆栈进行采样,即使是在挂钟时间,但是 结果如何 是测量(或热路径或热点,从中可以很容易隐藏“瓶颈”)。他们没有向你展示(而且他们很容易做到)的是实际样品本身。如果你的目标是 发现 瓶颈,你需要看到的数量是, 平均而言 ,2除以所需时间的分数。 因此,如果需要30%的时间,平均2/.3=6.7个样本会显示,20个样本显示的几率为99.2%。

    瓶颈可能是一个像这样的大斑点,也可能是许多小斑点,这没有什么区别。

    enter image description here

    抽样是垂直的。 如果有任何方法可以避免整个程序在那一刻所做的事情, 如果你在第二个样本上看到它 这就是区别所在——看看花时间的全部原因,而不仅仅是花了多少时间。

        2
  •  645
  •   Peter Mortensen Abd Al-Kareem Attiya    9 年前

    你可以用 Valgrind

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

    它将生成一个名为 callgrind.out.x . 然后您可以使用 kcachegrind

        3
  •  372
  •   try-catch-finally zloctb    7 年前

    我假设您使用的是GCC。标准解决方案是使用 gprof .

    一定要加上 -pg 要在分析之前进行编译,请执行以下操作:

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

    我还没试过,但我听说了一些好消息 google-perftools . 这绝对值得一试。

    here

    如果有的话,还有一些其他的流行语 gprof 不为您完成工作: Valgrind VTune DTrace .

        4
  •  283
  •   try-catch-finally zloctb    7 年前

    较新的内核(例如最新的Ubuntu内核)附带了新的“perf”工具( apt-get install linux-tools )阿卡 perf_events .

    这些都带有经典的采样分析器( man-page )以及令人敬畏的 timechart !

    系统配置文件 而且不仅仅是进程评测—它们可以显示线程、进程和内核之间的交互,并让您了解进程之间的调度和I/O依赖关系。

    Alt text

        5
  •  89
  •   Peter Mortensen Abd Al-Kareem Attiya    9 年前

    逃跑的答案 valgrind --tool=callgrind 如果没有一些选项,它还不太完整。我们通常不希望在Valgrind下评测10分钟的缓慢启动时间,而是希望在程序执行某些任务时评测它。

    valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp
    

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

    callgrind_control -i on
    

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

    callgrind_control -k
    

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

    kcachegrind callgrind.out.*
    

    我建议在下一个窗口中单击“Self”列标题,否则它会显示“main()”是最耗时的任务。“Self”表示每个函数本身花费了多少时间,而不是与依赖项一起。

        6
  •  84
  •   anon anon    15 年前

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

    (维基百科)Valgrind本质上是一个虚拟世界 使用准时制(JIT)的机器 编译技术,包括 动态重新编译。没有来自 相反,Valgrind首先翻译 将程序转换为临时的、更简单的形式 (IR),它是处理器中性的, 基于SSA的表格。转换后,, 不管它想要什么转变 在Valgrind翻译之前 IR返回到机器代码中,并允许 主机处理器运行它。

    Callgrind是一个基于此构建的分析器。主要的好处是,您不必运行应用程序数小时才能获得可靠的结果。因为Callgrind是一个很好的工具,所以即使是一秒钟的运行也足以得到可靠的结果 非探测 剖析器。

    另一个基于Valgrind的工具是Massif。我使用它来分析堆内存使用情况。它工作得很好。它所做的是为您提供内存使用情况的快照——详细信息,什么占内存的百分比,以及是谁把它放在那里的。这些信息在应用程序运行的不同时间点可用。

        7
  •  65
  •   HugoTeixeira    6 年前

    Nazgob's Gprof answer

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

    1. 除非使用 workaround

    2. 调用图被函数指针弄糊涂了。示例:我有一个名为 multithread() 这使我能够在指定数组(都作为参数传递)上多线程执行指定函数。但是,Gprof会查看对的所有调用 多线程() 在计算花在孩子身上的时间时,这是等效的。因为我把一些函数传递给 多线程() 比其他人花的时间长得多我的呼叫图基本上是无用的。(对于那些想知道线程是否是这里的问题的人:不, 多线程() 可以选择(在本例中是这样做的),仅在调用线程上按顺序运行所有内容)。

    3. 上面说 here “电话号码是通过计数而不是抽样得出的,它们是完全准确的…”。然而,我发现我的调用图给了我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下运行,如果这对任何人都有帮助的话。

        8
  •  39
  •   Ciro Santilli OurBigBook.com    3 年前

    C++剖析技术:GPROVS ValgRAND vs PFF与GPrSOOTHORKS

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

    • main 电话 fast maybe_slow 3次,其中一次 也许你会慢下来 电话慢

      缓慢的呼唤 也许你会慢下来 如果我们考虑对子函数的调用,则10X更长,并且支配运行时。 common

    • 二者都 快速的 也许你会慢下来 呼叫 常见的 ,它占程序执行的大部分

    • 程序界面为:

      ./main.out [n [seed]]
      

      而这个项目确实如此 O(n^2) seed 只是为了在不影响运行时的情况下获得不同的输出。

    main.c

    #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;
    }
    

    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
    

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

    gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
    ./main.out 10000
    

    第一 time 告诉我们执行时间有无 -pg 我们都是一样的,这很好:没有减速!然而,我也看到过复杂软件的2-3倍减速的报道,例如 shown in this ticket

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

    gprof2dot 被问及: Is it possible to get a graphical 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
    

    这里,这个 gprof 格蒙·奥特 跟踪信息,并在中生成可读的报告 main.gprof 哪一个 gprof2dot

    https://github.com/jrfonseca/gprof2dot

    我们遵守以下规定: -O0 运行:

    enter image description here

    -O3 运行:

    enter image description here

    这个 -O0 也许你会慢下来 调用及其子调用占总运行时的97.56%,尽管 也许你会慢下来 没有子函数的函数本身占总执行时间的0.00%,也就是说,在该函数中花费的几乎所有时间都花在子函数调用上。

    托多:为什么 主要的 -臭氧 输出,即使我可以在屏幕上看到它 bt Missing function from GProf output 我认为这是因为gprof除了编译的工具之外,还基于采样,并且 -臭氧 主要的 太快了,没有样品。

    我选择SVG输出而不是PNG,因为SVG可以用 + 文件大小可以小10倍左右。此外,对于复杂的软件和GNOME,生成的图像的宽度和高度可以达到数万像素 eog 3.28.1在这种情况下,PNG会出现错误,而SVG会通过我的浏览器自动打开。gimp 2.8运行良好,另请参见:

    this ticket :

    enter image description here

    你能很容易地找到最关键的调用堆栈吗?所有这些细小的未排序的意大利面线都在彼此之间?也许有更好的 dot 我肯定有选择,但我现在不想去那里。我们真正需要的是一个合适的专用查看器,但我还没有找到:

    或者,我们也可以观察 gprof 我们以前保存在以下位置的内置binutils工具:

    cat main.gprof
    

    一旦理解了数据输出格式,就可以减少冗长程度,只显示数据,而无需使用 -b 选项:

    gprof -b main.out
    

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

    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
    

    -臭氧 :

    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
    

                    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]
    

    maybe_flow ). [3]

    对于 ,如图中所示 快速的 没有一个已知的父母,这就是文档所说的 <spontaneous>

    我不确定是否有一种很好的方法可以使用gprof进行逐行分析: `gprof` time spent in particular lines of code

    瓦尔格兰·卡拉格兰

    Tools to get a pictorial function call graph of code

    callgrind是valgrind的评测代码的工具,kcachegrind是一个可以可视化cachegrind输出的KDE程序。

    首先,我们必须删除 -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
    

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

    时间 告诉我们程序执行花费了29.5秒,所以在这个例子中我们的速度降低了大约15倍。显然,这种放缓将严重限制更大的工作负载。关于“真实世界软件示例” mentioned here ,我观察到了80倍的减速。

    运行将生成名为的配置文件数据文件 callgrind.out.<pid> 例如 callgrind.out.8554

    kcachegrind callgrind.out.8554
    

    enter image description here

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

    enter image description here

    快速的 快速的 在左侧窗口中,它确实显示了一个带有 快速的 ,因此该堆栈实际上已被捕获。还没有人找到显示完整图形调用图的方法: Make callgrind show all function calls in the kcachegrind callgraph

    关于复杂C++软件的toDo,我看到一些类型的条目 <cycle N> <cycle 11> 在我期望函数名的地方,这意味着什么?我注意到有一个“循环检测”按钮来打开和关闭,但这是什么意思?

    perf 从…起 linux-tools

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

    sudo apt install linux-tools
    time perf record -g ./main.out 10000
    

    这为执行增加了0.2秒,因此我们在时间方面做得很好,但在扩展了 常见的 带有键盘右箭头的节点:

    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              
    

    因此,我尝试对 -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              
    

    -臭氧 处决是不是很简单 太快了,没有得到任何样本?它能和你一起工作吗 -臭氧 在执行时间较长的大型程序上?我是否错过了一些CLI选项?我发现了 -F -F 39500 (可随时间增加) sudo )我仍然看不到明确的要求。

    有一件很酷的事 性能 是Brendan Gregg提供的FlameGraph工具,它以一种非常简洁的方式显示调用堆栈计时,允许您快速查看大型调用。该工具可从以下网址获得: https://github.com/brendangregg/FlameGraph 在他的性能教程中也提到了: http://www.brendangregg.com/perf.html#FlameGraphs 当我跑的时候 性能 没有 苏多 我得到了 ERROR: No stack counts found 所以现在我要和你一起做 苏多 :

    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
    

    也许你会慢下来 也没有 快速的 在该图上:

    enter image description here

    在一个更复杂的例子中,图形的含义变得清晰:

    enter image description here

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

    另一个值得使用的perf GUI界面包括:

    gperf工具

    以前称为“谷歌性能工具”,来源: https://github.com/gperftools/gperftools 基于样本的。

    首先使用以下工具安装gperftools:

    sudo apt install google-perftools
    

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

    在运行时,我们必须通过设置 LD_PRELOAD 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
    

    或者,我们也可以在链接时构建库,而不必经过 预载 在运行时:

    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
    

    另见: gperftools - profile file not dumped

    google-pprof --callgrind main.out prof.out  > callgrind.out
    kcachegrind callgrind.out
    

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

    google-pprof --web main.out prof.out
    

    enter image description here

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

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

    google-pprof --text main.out prof.out
    

    其中:

    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
    

    How to use google perf tools

    perf_event_open 系统调用

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

    这对大多数人来说可能太过硬核了,但这很有趣。最小可运行示例位于: Quick way to count number of instructions executed in a C program

    英特尔VTune

    https://en.wikipedia.org/wiki/VTune

    这似乎是封闭源代码和x86,但据我所知,这可能是惊人的。我不确定它有多免费,但似乎可以免费下载。待评估。

    在Ubuntu 18.04、gprof2dot 2019.11.30、valgrind 3.13.0、perf 4.15.18、Linux内核4.15.0、FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b、gperftools 2.5-2中测试。

        9
  •  31
  •   Tim Miller    6 年前

    使用Valgrind、callgrind和kcachegrind:

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

    生成callgrind.out.x。使用kcachegrind阅读它。

    使用gprof(添加-pg):

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

    (对于多线程、函数指针不太好)

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

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

    其他:

        10
  •  6
  •   fwyzard    6 年前

    对于单线程程序,您可以使用 igprof ,不光彩的剖析者: https://igprof.org/ .

    这是一个采样剖面仪,沿着。。。长的Mike Dunlavey的回答,它将把结果包装在一个可浏览的调用堆栈树中,并用每个函数(累积或每个函数)花费的时间或内存进行注释。

        11
  •  6
  •   raovgarimella    6 年前

    值得一提的还有

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

    我使用过HPCToolkit和VTune,它们非常有效地找到了问题的症结所在,不需要重新编译代码(除了必须在CMake中使用-g-O或RelWithDebInfo类型的build来获得有意义的输出)。我听说TAU在能力上是相似的。

        12
  •  4
  •   seo    9 年前

    以下是我用来加速代码的两种方法:

    对于CPU限制的应用程序:

    1. 在调试模式下使用探查器来识别代码中有问题的部分

    对于I/O绑定的应用程序:

    1. 在发布模式下使用探查器来识别代码中有问题的部分。

    请注意

    对于CPU,在 调试 模式下,编译器将减少数学运算、向量化循环和内联函数,这些函数在汇编代码时往往会将代码整合到不可映射的混乱中。 . 如果您需要 释放

        13
  •  3
  •   N3UR0CHR0M    5 年前

    您可以使用iprof库:

    https://gitlab.com/Neurochrom/iprof

    https://github.com/Neurochrom/iprof

    完整免责声明:我是作者。

        14
  •  3
  •   BullyWiiPlaza    5 年前

    您可以使用日志框架,如 loguru 因为它包括时间戳和总正常运行时间,可以很好地用于分析:

        15
  •  3
  •   Moritz Barsnick asio_guy    3 年前

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

    1. 我的算法正确吗?
    2. 有没有被证明是瓶颈的锁?
    3. 是否有一段代码被证明是罪魁祸首?

    valgrind 结合 卡尔格里德 kcachegrind 应该对以上几点提供一个合理的估计,一旦确定某些代码部分存在问题,我建议做一个微基准- google benchmark 这是一个很好的开始。

        16
  •  2
  •   SOKS    5 年前

    在工作中,我们有一个非常好的工具,可以帮助我们监控我们想要的日程安排。这已经有用了很多次。

    它是C++的,必须根据你的需要定制。不幸的是,我不能共享代码,只能共享概念。 你用的是“大” volatile 包含时间戳和事件ID的缓冲区,您可以在事后或停止日志记录系统后转储(例如,将其转储到文件中)。

    您用所有数据检索所谓的大缓冲区,一个小接口解析它,并用名称(向上/向下+值)显示事件,就像示波器用颜色(在中配置)显示事件一样 .hpp 文件)。

    您可以自定义生成的事件数量,以便只关注您想要的内容。它在调度问题上帮助了我们很多,同时根据每秒记录的事件量消耗了我们想要的CPU量。

    您需要3个文件:

    toolname.hpp // interface
    toolname.cpp // code
    tool_events_id.hpp // Events ID
    

    其概念是在 tool_events_id.hpp 就像这样:

    // EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
    #define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
    #define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv
    

    您还可以在中定义一些函数 toolname.hpp

    #define LOG_LEVEL_ERROR 0
    #define LOG_LEVEL_WARN 1
    // ...
    
    void init(void);
    void probe(id,payload);
    // etc
    

    在代码中的任何地方,您都可以使用:

    toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);
    

    这个 probe 函数使用一些装配线尽快检索时钟时间戳,然后在缓冲区中设置一个条目。我们还有一个原子增量,用于安全地找到存储日志事件的索引。 当然,缓冲区是循环的。

    希望这个想法不会因为缺少示例代码而变得模糊。

        17
  •  1
  •   Pejvak    5 年前

    使用 -pg 编译和链接代码并运行可执行文件时的标记。执行此程序时,分析数据收集在文件a.out中。

    1-平面轮廓:
    通过运行命令 gprog --flat-profile a.out 你得到了以下数据
    -整个活动花费的时间百分比是多少,
    -在一个函数(包括和不包括对子函数的调用)中花费了多少秒,
    -电话的数量,

    2-图分析
    指挥部 gprof --graph a.out 获取每个函数的以下数据,包括
    -在每个部分中,一个函数用索引号标记。
    -在函数上方,有一个调用函数的函数列表。
    -函数下面是该函数调用的函数列表。

    要获得更多信息,您可以查看 https://sourceware.org/binutils/docs-2.32/gprof/

        18
  •  1
  •   user10306682 user10306682    4 年前

    如何识别代码运行缓慢的地方?

    当你在运动中遇到障碍物时,它会降低你的速度

    像不必要的重新分配循环、缓冲区溢出、搜索、内存泄漏等操作会消耗更多的执行能力,这将对代码的性能产生不利影响, 在分析之前,请确保将-pg添加到编译中:

    g++ your_prg.cpp -pg cc my_program.cpp -g -pg 根据你的编译器

    我还没试过,但我听说过谷歌perftools的好东西。这绝对值得一试。

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

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

    我认为是这样

        19
  •  0
  •   Wei    5 年前

    由于没有人提到ARM图,我将把它作为个人,我已经成功地使用地图来配置C++科学程序。

    ARM映射是用于并行、多线程或单线程C、C++、FORTRAN和F90代码的剖析器。它提供了对源代码行的深入分析和瓶颈定位。与大多数分析器不同,它的设计目的是能够为并行和线程代码评测pthreads、OpenMP或MPI。

    地图是商业软件。