如何分析在 Linux 上运行的 C++ 代码?

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

提问人:Gabriel Isenberg 提问时间:12/18/2008 最后编辑:Mateen UlhaqGabriel Isenberg 更新时间:11/13/2023 访问量:699130

问:

如何找到在 Linux 上运行的 C++ 应用程序中运行缓慢的代码区域?

C++ Linux 分析

评论

2赞 Kapil Gupta 5/22/2012
已经在以下链接上回答了:stackoverflow.com/questions/2497211/...
7赞 artless noise 3/18/2013
大多数答案都是分析器。但是,优先级反转、缓存别名、资源争用等都可能是影响优化和性能的因素。我认为人们会从我缓慢的代码中读取信息。常见问题解答引用了此线程。code
5赞 maddouri 10/12/2015
CppCon 2015:Chandler Carruth “Tuning C++: Benchmarks, CPUs, and Compilers!天哪!
5赞 Jose Manuel Gomez Alvarez 12/15/2016
我曾经随机使用 pstack,大多数时候会打印出程序大部分时间所在的最典型的堆栈,因此指出了瓶颈。
1赞 v.oddou 2/22/2019
cachegrind/callgrind ?

答:

387赞 Nazgob 12/18/2008 #1

我假设您正在使用 GCC。标准解决方案是使用 gprof 进行分析。

在分析之前,请务必添加到编译中:-pg

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

我还没有尝试过,但我听说过关于 google-perftools 的好消息。绝对值得一试。

相关问题在这里

如果不能为您完成这项工作,其他一些流行语:Valgrind、Intel VTune、Sun DTracegprof

评论

6赞 Bill the Lizard 12/18/2008
我同意 gprof 是当前标准。不过,请注意,Valgrind 用于分析内存泄漏和程序的其他内存相关方面,而不是用于速度优化。
73赞 Salvatore Dario Minonne 12/18/2008
Bill, 在 vaglrind 套件中,您可以找到 callgrind 和 massif。两者对于分析应用程序都非常有用
8赞 Mike Dunlavey 3/4/2010
蜥蜴@Bill:关于gprof的一些评论:stackoverflow.com/questions/1777556/alternatives-to-gprof/...
7赞 Krazy Glew 4/28/2012
gprof -pg 只是调用堆栈分析的近似值。它插入 mcount 调用以跟踪哪些函数正在调用哪些其他函数。它使用标准基于时间的采样,呃,时间。然后,它将函数 foo() 中采样的时间分配回 foo() 的调用者,以分配给调用的编号器。因此,它不区分不同成本的调用。
2赞 einpoklum 10/14/2019
使用 clang/clang++,可以考虑使用 gperftools 的 CPU 分析器。注意:我自己没有这样做过。
1605赞 27 revs, 5 users 82%Mike Dunlavey #2

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

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

在像 gdb 这样的调试器中执行代码,停止它,每次多次查看调用堆栈(例如回溯)。如果有一些代码浪费了一定比例的时间,20% 或 50% 或其他时间,那么这就是您在每个样本的操作中捕获它的概率。所以,这大约是你会看到它的样本的百分比。不需要有根据的猜测。如果您确实猜测问题是什么,这将证明或反驳它。

您可能有多个不同大小的性能问题。如果您清除其中任何一个,其余的将占据更大的百分比,并且更容易在后续传递中被发现。当这种放大效应叠加在多个问题上时,可以导致真正巨大的加速系数。

注意:程序员往往对这种技术持怀疑态度,除非他们自己使用过。他们会说探查器会为您提供此信息,但只有当它们对整个调用堆栈进行采样,然后让您检查一组随机样本时,这才是正确的。(摘要是失去洞察力的地方。调用图不会为您提供相同的信息,因为

  1. 他们不会在教学层面进行总结,并且
  2. 它们在存在递归的情况下给出令人困惑的摘要。

他们还会说它只适用于玩具程序,而实际上它适用于任何程序,而且它似乎在更大的程序上效果更好,因为他们往往有更多的问题需要发现。他们会说,它有时会发现一些不是问题的东西,但只有当你看到一次东西时,这才是正确的。如果您在多个样本上发现问题,则该问题是真实的。

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

P.P.S 作为一个粗略的一般情况,你的软件中的抽象层越多,你就越有可能发现这是性能问题的原因(以及获得加速的机会)。

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

我经常听到的另一个反对意见是:“它会在某个地方随机停止,它会错过真正的问题”。 这来自于对真正问题是什么有一个先验的概念。 性能问题的一个关键属性是它们违背了预期。 抽样告诉你某件事有问题,你的第一反应是难以置信。 这是很自然的,但你可以确定,如果它发现一个问题,它是真实的,反之亦然。

补充:让我对它的工作原理做一个贝叶斯解释。假设有一些指令(调用或其他指令)在调用堆栈上花费了一小部分时间(因此成本很高)。为简单起见,假设我们不知道是什么,但假设它是 0.1、0.2、0.3、...0.9、1.0,而这些可能性中的每一个的先验概率都是 0.1,因此所有这些成本都是先验的。Iff

然后假设我们只取 2 个堆栈样本,我们看到两个样本的指令,指定的观察值。这为我们提供了对 的频率的新估计,据此:Io=2/2fI

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                

例如,最后一列说,>= 0.5 的概率为 92%,高于先前的假设 60%。f

假设先前的假设不同。假设我们假设是 .991(几乎可以肯定),所有其他可能性几乎是不可能的 (0.001)。换句话说,我们先验的确定性是便宜的。然后我们得到:P(f=0.1)I

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                

现在它说是26%,高于之前的0.6%。因此,贝叶斯允许我们更新对 的可能成本的估计。如果数据量很小,它并不能准确地告诉我们成本是多少,只是它足够大,值得修复。P(f >= 0.5)I

另一种看待它的方式被称为继承规则。 如果你掷硬币 2 次,两次正面朝上,这告诉你硬币的可能权重是什么? 受人尊敬的回答方式是说这是一个 Beta 分布,具有平均值。(number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%

(关键是我们不止一次看到。如果我们只看到它一次,除了 0 >之外,这并不能告诉我们太多。If

因此,即使是极少数的样本也可以告诉我们很多关于它所看到的指令的成本。(平均而言,它将以与其成本成正比的频率看到它们。如果取样,并且是成本,则会出现在样品上。例如,即样本。nfInf+/-sqrt(nf(1-f))n=10f=0.33+/-1.4


补充:为了直观地了解测量和随机堆栈采样之间的区别:
现在有分析器对堆栈进行采样,即使是在挂钟时间,但结果是测量值(或热路径,或热点,可以很容易地隐藏“瓶颈”)。他们没有向您展示(他们很容易)的是实际样本本身。如果你的目标是找到瓶颈,你需要看到的瓶颈数量平均是 2 除以所需的时间分数。 因此,如果它需要 30% 的时间,平均 2/.3 = 6.7 个样本将显示它,而 20 个样本显示它的几率为 99.2%。

下面是检查测量值和检查堆栈样品之间差异的即兴说明。 瓶颈可能是像这样的一个大斑点,也可能是许多小斑点,这没有区别。

enter image description here

测量是水平的;它告诉您特定例程所需的时间比例。 采样是垂直的。 如果有任何方法可以避免整个程序在那一刻所做的事情,并且如果您在第二个样本上看到它,那么您已经找到了瓶颈。 这就是与众不同的地方——看到花费时间的全部原因,而不仅仅是花了多少钱。

评论

336赞 Crashworks 5/23/2009
这基本上是一个穷人的抽样分析器,这很好,但你冒着样本量太小的风险,这可能会给你带来完全虚假的结果。
117赞 Mike Dunlavey 5/23/2009
@Crash:我不会争论“穷人”的部分:-)诚然,统计测量精度需要许多样本,但有两个相互冲突的目标——测量和问题定位。我关注的是后者,为此你需要精确的位置,而不是测量的精确。例如,在中间堆栈中,可以有一个函数调用 A();这占了 50% 的时间,但它可以在另一个大型函数 B 中,以及许多其他成本不高的对 A() 的调用。函数时间的精确摘要可能是一个线索,但其他每个堆栈样本都会查明问题所在。
51赞 Mike Dunlavey 5/25/2009
...世界似乎认为,用呼叫计数和/或平均时间注释的呼叫图就足够了。事实并非如此。可悲的是,对于那些对调用堆栈进行采样的人来说,最有用的信息就在他们面前,但他们为了“统计”的利益而将其扔掉。
39赞 Crashworks 6/2/2009
我并不是不同意你的技术。显然,我非常依赖堆栈遍历采样分析器。我只是指出,现在有一些工具可以自动完成,当你已经过了将函数从 25% 降低到 15% 并且需要将其从 1.2% 降低到 0.6% 时,这一点很重要。
20赞 Sam Harwell 4/8/2010
-1:这个想法很好,但如果你在一个适度以绩效为导向的环境中工作,这都是在浪费每个人的时间。使用真正的分析器,这样我们就不必跟在你身后解决实际问题。
698赞 Ajay 4/21/2009 #3

Valgrind 与以下选项一起使用:

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

这将生成一个名为 的文件。使用该工具读取此文件。它将为您提供事物的图形分析,并得出诸如哪些线路成本多少之类的结果。callgrind.out.xkcachegrind

评论

72赞 neves 1/26/2012
Valgrind 很棒,但请注意,它会让你的程序变慢
39赞 Sebastian 5/22/2013
另请查看 Gprof2Dot 以提供一种可视化输出的惊人替代方法。./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
6赞 enthusiasticgeek 5/23/2013
@neves 是的,Valgrind 在实时分析“gstreamer”和“opencv”应用程序的速度方面不是很有帮助。
4赞 John Zwinck 4/27/2017
@Sebastian:现在在这里:github.com/jrfonseca/gprof2dotgprof2dot
4赞 BIOStheZerg 10/29/2019
要记住的一件事是编译包含调试符号但进行优化,以获得一些可探索的东西,但速度特性类似于实际的“发布”构建。
87赞 anon 5/23/2009 #4

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

(维基百科)Valgrind 本质上是一个虚拟的 使用实时 (JIT) 的计算机 编译技术,包括 动态重新编译。什么都没有 原始程序会运行 直接在主机处理器上。 相反,Valgrind 首先翻译了 将程序转换为临时的、更简单的形式 称为中间表示 (IR),它是处理器中立的, 基于 SSA 的表单。转换后, 一个工具(见下文)是免费的 无论它想要什么转变 在 IR 上,在 Valgrind 翻译之前 将 IR 重新转换为机器代码,并让 主机处理器运行它。

Callgrind 是一个基于此构建的分析器。主要好处是您不必运行数小时即可获得可靠的结果。即使是一秒钟的运行也足以获得坚如磐石的可靠结果,因为 Callgrind 是一款非探测分析器。

另一个基于 Valgrind 构建的工具是 Massif。我用它来分析堆内存使用情况。效果很好。它的作用是给你内存使用情况的快照 -- 详细的信息是什么拥有多少百分比的内存,以及世卫组织把它放在那里。此类信息在应用程序运行的不同时间点可用。

297赞 Will 8/17/2010 #5

较新的内核(例如最新的 Ubuntu 内核)带有新的“perf”工具(又名 perf_events)。apt-get install linux-tools

这些都带有经典的采样分析器(手册页)以及令人敬畏的时间表

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

Alt text

评论

14赞 kizzx2 10/1/2010
很棒的工具!无论如何,我是否可以获得从“main->func1->fun2”样式开始的典型“蝴蝶”视图?我似乎想不通...... 似乎给了我带有调用父母的函数名称......(所以它有点像倒置的蝴蝶视图)perf report
1赞 osgx 12/6/2011
Will,可以显示线程活动的时间表;添加了 CPU 编号信息?我想查看每个 CPU 上何时以及哪个线程正在运行。
3赞 dashesy 5/15/2012
@kizzx2 - 您可以使用 和 .非常好的工具!gprof2dotperf script
3赞 Andrew Stern 10/13/2017
甚至像 4.13 这样的较新的内核也有用于分析的 eBPF。请参阅 brendangregg.com/blog/2015-05-15/ebpf-one-small-step.htmlbrendangregg.com/ebpf.html
2赞 Jorge Bellon 12/17/2018
这应该是公认的答案。使用调试器会在示例中引入过多的噪声。Linux 的性能计数器适用于多个线程、多个进程、用户和内核空间,这很棒。您还可以检索许多有用的信息,例如分支和缓存未命中。在@AndrewStern提到的同一个网站中,有一个火焰图对这种分析非常有用:火焰图。它生成的SVG文件可以用Web浏览器打开,用于交互式图形!
68赞 Rob_before_edits 7/1/2011 #6

这是对 Nazgob 的 Gprof 答案的回应。

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

  1. 它不能在多线程代码上正常工作,除非你使用解决方法

  2. 调用图被函数指针混淆。示例:我有一个名为的函数,它使我能够在指定的数组上对指定的函数进行多线程处理(两者都作为参数传递)。然而,Gprof 认为所有调用都是等价的,用于计算在儿童身上花费的时间。由于我传递的某些函数比其他函数花费的时间要长得多,因此我的调用图大多是无用的。(对于那些想知道线程是否是这里的问题的人:不,可以选择,在这种情况下,仅在调用线程上按顺序运行所有内容)。multithread()multithread()multithread()multithread()

  3. 在这里说“......呼叫次数数字是通过计数得出的,而不是抽样得出的。他们是完全准确的......”。然而,我发现我的呼叫图为我提供了 5345859132+784984078 作为我最常调用函数的呼叫统计信息,其中第一个数字应该是直接呼叫,第二个递归呼叫(它们都来自它本身)。由于这意味着我有一个错误,所以我在代码中放入了长(64 位)计数器并再次运行相同的计数器。我的计数:5345859132直接调用,78094395406自递归调用。那里有很多数字,所以我要指出,我测量的递归调用是 780 亿,而 Gprof 是 784m:相差 100 倍。两次运行都是单线程和未优化的代码,一个编译,另一个 .-g-pg

这是在 64 位 Debian Lenny 下运行的 GNU Gprof (GNU Binutils for Debian) 2.18.0.20080103,如果对任何人有帮助的话。

评论

1赞 Rob_before_edits 6/22/2012
是的,它确实可以抽样,但不会对呼叫次数进行抽样。有趣的是,点击您的链接最终将我带到了我在帖子中链接到的手册页面的更新版本,新 URL:sourceware.org/binutils/docs/gprof/...这重复了我回答的第 (iii) 部分中的引文,但也说“在多线程应用程序或与多线程库链接的单线程应用程序中,只有当计数函数是线程安全的时,计数才是确定性的。(注意:请注意,glibc 中的 mcount 计数函数不是线程安全的)。
1赞 Rob_before_edits 6/22/2012
我不清楚这是否解释了我在(iii)中的结果。我的代码链接了 -lpthread -lm 并声明了“pthread_t *thr”和“pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER”静态变量,即使它是单线程运行的。我通常认为“与多线程库链接”意味着实际使用这些库,而且在更大程度上,但我可能是错的!
99赞 Tõnu Samuel 6/8/2012 #7

如果没有一些选项,运行的答案并不完全完整。我们通常不想在 Valgrind 下分析 10 分钟的慢启动时间,而是希望在程序执行某些任务时对其进行分析。valgrind --tool=callgrind

所以这是我推荐的。首先运行程序:

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()”是最耗时的任务。“自我”显示了每个功能本身花费了多少时间,而不是与依赖者一起花费的时间。

评论

13赞 Tõnu Samuel 7/31/2014
现在由于某种原因,callgrind.out.* 文件总是空的。执行 callgrind_control -d 对于强制将数据转储到磁盘非常有用。
4赞 Tõnu Samuel 11/22/2015
不能。我通常的上下文是整个MySQL或PHP或类似的大东西。很多时候甚至一开始都不知道自己想分开什么。
3赞 Code Abominator 3/17/2016
或者就我而言,我的程序实际上将一堆数据加载到 LRU 缓存中,我不想对其进行分析。因此,我在启动时强制加载缓存的子集,并仅使用该数据分析代码(让 OS+CPU 管理缓存中的内存使用)。它有效,但是加载缓存的速度很慢,并且跨我试图在不同的上下文中分析的代码占用大量 CPU,因此 callgrind 会产生严重污染的结果。
3赞 Andre Holzner 8/30/2017
还有以编程方式启用/禁用收集;查看 stackoverflow.com/a/13700817/288875CALLGRIND_TOGGLE_COLLECT
2赞 explorer 6/4/2020
@TõnuSamuel,对我来说,callgrind.out.*也是空的。就我而言,该程序在分析时被破坏了。解决崩溃原因后,我能够看到callgrind.out.*文件中的内容。
4赞 seo 11/29/2013 #8

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

对于受 CPU 限制的应用程序:

  1. 在 DEBUG 模式下使用探查器来识别代码的可疑部分
  2. 然后切换到 RELEASE 模式并注释掉代码中可疑的部分(不带任何内容存根),直到您看到性能变化。

对于 I/O 密集型应用程序:

  1. 在 RELEASE 模式下使用探查器来识别代码中的可疑部分。

注意

如果您没有分析器,请使用穷人的分析器。在调试应用程序时按暂停键。大多数开发人员套件都会使用注释行号进入程序集。从统计上讲,您可能会降落在占用大部分 CPU 周期的区域。

对于 CPU,在 DEBUG 模式下进行性能分析的原因是,如果您尝试在 RELEASE 模式下进行性能分析,编译器将减少数学运算、矢量化循环和内联函数,这往往会使您的代码在组装时陷入不可映射的混乱状态。不可映射的混乱意味着您的探查器将无法清楚地识别需要很长时间的内容,因为程序集可能与优化中的源代码不对应。如果您需要 RELEASE 模式的性能(例如时序敏感),请根据需要禁用调试器功能以保持可用的性能。

对于 I/O 绑定,探查器仍然可以在 RELEASE 模式下识别 I/O 操作,因为 I/O 操作要么在外部链接到共享库(大多数时候),要么在最坏的情况下,将导致系统调用中断向量(探查器也很容易识别)。

评论

2赞 Mike Dunlavey 6/28/2014
+1 穷人的方法对 I/O 绑定和 CPU 绑定一样有效,我建议在 DEBUG 模式下进行所有性能调整。完成调整后,打开 RELEASE。如果程序在您的代码中受 CPU 限制,它将进行改进。这是一个粗略但简短的视频。
4赞 gast128 7/22/2014
我不会使用 DEBUG 版本进行性能分析。我经常看到,DEBUG模式下的性能关键部件在发布模式下被完全优化。另一个问题是在调试代码中使用断言,这会增加性能的噪音。
3赞 seo 7/22/2014
你读过我的帖子吗?“如果您需要 RELEASE 模式的性能(例如时序敏感),请根据需要禁用调试器功能以保持可用的性能”,“然后切换到 RELEASE 模式并注释代码的可疑部分(不留任何内容)直到您看到性能变化。我说过在调试模式下检查可能的问题区域,并在发布模式下验证这些问题,以避免您提到的陷阱。
33赞 Ehsan 2/24/2017 #9

使用 Valgrind、callgrind 和 kcachegrind:

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

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

使用 gprof (add -pg):

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

(不太适合多线程,函数指针)

使用 google-perftools:

使用时间采样,I/O 和 CPU 瓶颈暴露无遗。

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

别人:AMD Codeanalyan(后来被 AMD CodeXL 取代)、OProfile、“perf”工具(apt-get install linux-tools)

7赞 fwyzard 3/17/2018 #10

对于单线程程序,您可以使用 igprof, The Ignominous Profiler: https://igprof.org/

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

评论

0赞 Basile Starynkevitch 1/12/2020
它看起来很有趣,但无法使用 GCC 9.2 编译。(Debian/Sid)我在 github 上提出了一个问题。
8赞 raovgarimella 9/15/2018 #11

另外值得一提的是

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

我使用了 HPCToolkit 和 VTune,它们在帐篷中找到长杆非常有效,并且不需要重新编译您的代码(除了您必须在 CMake 中使用 -g -O 或 RelWithDebInfo 类型构建才能获得有意义的输出)。我听说 TAU 的功能类似。

4赞 N3UR0CHR0M 2/25/2019 #12

您可以使用 iprof 库:

https://gitlab.com/Neurochrom/iprof

https://github.com/Neurochrom/iprof

它是跨平台的,允许您不实时测量应用程序的性能。您甚至可以将其与实时图形结合使用。 完全免责声明:我是作者。

3赞 SOKS 5/17/2019 #13

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

它采用 C++ 语言,必须根据您的需求进行自定义。不幸的是,我不能分享代码,只能分享概念。 您可以使用包含时间戳和事件 ID 的“大型”缓冲区,您可以在事后或停止日志记录系统后转储这些缓冲区(例如,并将其转储到文件中)。volatile

您可以检索包含所有数据的所谓大缓冲区,然后一个小接口解析它并显示带有名称(up/down + value)的事件,就像示波器使用颜色(在文件中配置)一样。.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

希望这个想法不会因为缺少示例代码而混淆。

4赞 BullyWiiPlaza 5/21/2019 #14

您可以使用像 loguru 这样的日志记录框架,因为它包含时间戳和总正常运行时间,可以很好地用于分析:

评论

1赞 sehe 6/28/2023
这与分析有什么关系?
0赞 BullyWiiPlaza 6/28/2023
@sehe:您可以通过日志时间戳查看所需的时间
1赞 Wei 6/28/2019 #15

由于没有人提到 Arm MAP,我会添加它,因为我个人已经成功地使用 Map 来分析 C++ 科学程序。

Arm MAP 是并行、多线程或单线程 C、C++、Fortran 和 F90 代码的分析器。它提供了深入的分析和瓶颈,精确定位了源线。与大多数分析器不同,它被设计为能够分析并行和线程代码的 pthreads、OpenMP 或 MPI。

MAP是商业软件。

5赞 asio_guy 11/3/2019 #16

实际上有点惊讶,没有多少人提到google / benchmark,虽然固定代码的特定区域有点麻烦,特别是如果代码库有点大,但是我发现这在结合使用时真的很有帮助callgrind

恕我直言,确定导致瓶颈的部分是这里的关键。但是,我会先尝试回答以下问题,然后根据此选择工具

  1. 我的算法正确吗?
  2. 是否有被证明是瓶颈的锁?
  3. 是否有特定的代码部分被证明是罪魁祸首?
  4. IO怎么样,处理和优化?

valgrind结合 和 应该对上述几点进行体面的估计,一旦确定某些代码部分存在问题,我建议做一个微基准 - 是一个很好的起点。callgrindkcachegrindgoogle benchmark

评论

0赞 Rahul Ravindran 6/20/2020
我发现,当我测量代码部分时,我的谷歌基准测试数字看起来比 gprof 更准确。正如你所说,它真的很适合微基准测试。但是,如果您想要更全面的画面,则需要不同的方法。
0赞 mehdi_bm 12/7/2019 #17

编译和链接代码时使用 flag,并运行可执行文件。执行此程序时,分析数据收集在文件 a.out 中。
有两种不同类型的分析
-pg

1- 平面分析:
通过运行命令,您可以获得以下数据
- 函数花费的总时间百分比, - 函数花费了多少秒 - 包括和排除对子函数的调用, - 调用次数,


- 每次调用的平均时间。
gprog --flat-profile a.out

2- 图形分析
我们的命令,以获取每个函数的以下数据,其中包括
- 在每个部分中,一个函数标有索引号。
- 上面的函数,有一个调用该函数的函数列表。
- 在函数下面,有一个由函数调用的函数列表。
gprof --graph a.out

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

76赞 Ciro Santilli OurBigBook.com 2/17/2020 #18

C++分析技术调查:gprof vs valgrind vs perf vs gperftools

在这个答案中,我将使用几种不同的工具来分析一些非常简单的测试程序,以便具体比较这些工具的工作原理。

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

  • main通话和 3 次,其中一个通话很慢fastmaybe_slowmaybe_slow

    的慢速调用时间长 10 倍,如果我们考虑对子函数的调用,则在运行时占主导地位。理想情况下,分析工具将能够将我们指向特定的慢速调用。maybe_slowcommon

  • 和 call ,它占了程序执行的大部分fastmaybe_slowcommon

  • 程序接口为:

    ./main.out [n [seed]]
    

    并且该程序总共循环。 只是为了在不影响运行时的情况下获得不同的输出。O(n^2)seed

主.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需要使用检测重新编译软件,并且它还将采样方法与该检测一起使用。因此,它在准确性(采样并不总是完全准确,可以跳过函数)和执行速度(检测和采样是相对较快的技术,不会大大减慢执行速度)之间取得平衡。

gprof 内置于 GCC/binutils 中,因此我们所要做的就是使用启用 gprof 的选项进行编译。然后,我们使用 size CLI 参数正常运行程序,该参数产生几秒钟的合理持续时间 ():-pg10000

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

首先,告诉我们有和没有的执行时间是一样的,这很好:没有减速!然而,我见过复杂软件速度减慢 2 到 3 倍的记录,例如这张票所示time-pg

因为我们使用 编译,所以运行程序会生成一个包含分析数据的文件文件。-pggmon.out

我们可以以图形方式观察该文件,如以下位置所问:是否可以获得 gprof 结果的图形表示?gprof2dot

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

在这里,该工具读取跟踪信息,并在 中生成人类可读的报告,然后读取该报告以生成图形。gprofgmon.outmain.gprofgprof2dot

gprof2dot 的来源位于: https://github.com/jrfonseca/gprof2dot

我们在运行中观察到以下几点:-O0

enter image description here

对于运行:-O3

enter image description here

输出几乎是不言自明的。例如,它显示 3 个调用及其子调用占用了总运行时的 97.56%,尽管没有子调用的执行本身占总执行时间的 0.00%,即几乎所有在该函数中花费的时间都花在了子调用上。-O0maybe_slowmaybe_slow

TODO:为什么输出中缺少它,即使我可以在 GDB 中看到它?GProf 输出中缺少函数我认为这是因为 gprof 除了其编译的仪器之外,还基于采样,而且速度太快并且没有样本。main-O3bt-O3main

我选择 SVG 输出而不是 PNG,因为 SVG 可以用 + 搜索,而且文件大小可以小约 10 倍。此外,对于复杂的软件来说,生成的图像的宽度和高度可能会有数万像素,而 GNOME 3.28.1 在这种情况下会出现 PNG 的错误,而 SVG 会自动由我的浏览器打开。不过,GIMP 2.8 运行良好,另请参阅:CtrlFeog

但即便如此,你也会经常拖动图像来找到你想要的东西,例如,请看这张来自这张票的“真实”软件示例的这张图片:

enter image description here

你能在所有这些微小的未分类的意大利面条线相互重叠的情况下轻松找到最关键的呼叫堆栈吗?我敢肯定,可能会有更好的选择,但我现在不想去那里。我们真正需要的是一个合适的专用查看器,但我还没有找到一个:dot

但是,您可以使用颜色映射表来稍微缓解这些问题。例如,在上一张巨大的图像上,当我做出绿色在红色之后,最后是深蓝色和深蓝色的精彩推论时,我终于设法找到了左边的关键路径。

或者,我们也可以观察我们之前保存在的内置 binutils 工具的文本输出:gprof

cat main.gprof

默认情况下,这将生成一个非常详细的输出,用于解释输出数据的含义。既然我不能比这更好的解释,我就让你自己读。

了解数据输出格式后,可以使用以下选项减少详细程度以仅显示数据,而无需教程:-b

gprof -b main.out

在我们的示例中,输出为:-O0

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

以及:-O3

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]

以左缩进的函数为中心 ()。 是该函数的 ID。函数上方是其调用方,其下方是被调用方。maybe_flow[3]

对于 ,请参阅此处,如图形输出中所示,并且没有已知的父级,这就是文档所说的含义。-O3maybe_slowfast<spontaneous>

我不确定是否有一种很好的方法可以使用 gprof 进行逐行分析:“gprof”在特定代码行上花费的时间

Valgrind Callgrind

Valgrind 通过 Valgrind 虚拟机运行程序。这使得分析非常准确,但它也会导致程序的减速非常大。我之前在: 获取代码图形函数调用图的工具

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

首先,我们必须删除该标志才能恢复正常编译,否则运行实际上会失败,并且 Profiling 计时器已过期,是的,这很常见,我这样做了,并且有一个 Stack Overflow 问题。-pg

因此,我们编译并运行为:

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 倍。显然,这种速度减慢对于较大的工作负载来说将是一个严重的限制。在这里提到的“真实世界的软件示例”中,我观察到速度减慢了 80 倍。time

运行将生成一个名为 就我而言。我们查看该文件:callgrind.out.<pid>callgrind.out.8554

kcachegrind callgrind.out.8554

它显示了一个 GUI,其中包含类似于文本 gprof 输出的数据:

enter image description here

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

enter image description here

我认为没有显示在该图上,因为 kcachegrind 必须简化了可视化,因为该调用占用的时间太少,这可能是您在真实程序上想要的行为。右键单击菜单有一些设置来控制何时剔除此类节点,但是在快速尝试后,我无法让它显示如此短的调用。如果我单击左侧窗口,它确实会显示一个带有 的调用图,因此该堆栈实际上被捕获了。还没有人找到一种方法来显示完整的图调用图:让 callgrind 显示 kcachegrind 调用图中的所有函数调用fastfastfast

在复杂的C++软件上做TODO,我看到一些类型的条目,例如 我期望函数名称,这意味着什么?我注意到有一个“周期检测”按钮可以打开和关闭它,但这是什么意思?<cycle N><cycle 11>

来自 linux-tools 的 perf

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

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

这增加了 0.2 秒的执行时间,所以我们在时间上很好,但在使用键盘向右箭头展开节点后,我仍然没有看到太多兴趣:common

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              

TODO:处决时发生了什么?仅仅是这样,速度太快,没有得到任何样品吗?它是否适用于需要更长时间执行的大型程序?我是否错过了一些 CLI 选项?我发现要以赫兹为单位控制采样频率,但我将其调高到默认允许的最大值(可以增加),但我仍然没有看到清晰的调用。-O3maybe_slowfast-O3-F-F 39500sudo

Brendan Gregg 的 FlameGraph 工具很酷,它以非常简洁的方式显示呼叫堆栈时间,让您可以快速查看大型呼叫。该工具可在以下位置获得: https://github.com/brendangregg/FlameGraph 并且在他的性能教程中也提到了: http://www.brendangregg.com/perf.html#FlameGraphs 当我在没有的情况下运行时,我得到了错误:没有找到堆栈计数,所以现在我将使用:perfperfsudosudo

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

但是在这样一个简单的程序中,输出不是很容易理解,因为我们不容易在该图上看到或:maybe_slowfast

enter image description here

在一个更复杂的例子中,很明显该图的含义:

enter image description here

TODO 该示例中有函数日志,这是为什么?[unknown]

另一个可能值得的性能 GUI 界面包括:

  • Eclipse Trace Compass 插件:https://www.eclipse.org/tracecompass/

    但这有一个缺点,您必须首先将数据转换为通用跟踪格式,这可以通过 来完成,但需要在构建时启用它/有足够的新数据,这两种情况都不是 Ubuntu 18.04 中的 perf 的情况perf data --to-ctfperf

  • https://github.com/KDAB/hotspot

    这样做的缺点是似乎没有 Ubuntu 软件包,构建它需要 Qt 5.10,而 Ubuntu 18.04 是 Qt 5.9。

    David Faure 在评论中提到,没有 AppImage 包,这可能是一种方便的使用方式。

gperftools

以前称为“Google Performance Tools”,来源:https://github.com/gperftools/gperftools 基于示例。

首先使用以下命令安装 gperftools:

sudo apt install google-perftools

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

在运行时,我们必须将 set to point 传递给 ,你可以用 找到它,例如在我的系统上:LD_PRELOADlibprofiler.solocate 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

或者,我们可以在链接时构建库,在运行时分配传递:LD_PRELOAD

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 - 配置文件未转储

到目前为止,我发现查看这些数据的最好方法是使 pprof 输出与 kcachegrind 相同的格式作为输入(是的,Valgrind-project-viewer-tool)并使用 kcachegrind 查看:

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

使用其中任一方法运行后,我们会得到一个配置文件数据文件作为输出。我们可以使用以下命令以 SVG 形式以图形方式查看该文件:prof.out

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

Смотритетакже: 如何使用谷歌性能工具

使用原始perf_event_open系统调用检测代码

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

对于大多数人来说,这可能太硬核了,但它很有趣。最小可运行示例:快速计算 C 程序中执行的指令数的方法

英特尔 VTune

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

这似乎是闭源的,仅限 x86,但从我所听到的来看,它可能是惊人的。我不确定它的使用免费程度,但它似乎是免费下载的。TODO 评估。

在 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 中进行了测试。

评论

5赞 Jorge Bellon 3/30/2020
默认情况下,性能记录使用帧指针寄存器。现代编译器不记录帧地址,而是将寄存器用作通用用途。另一种方法是使用标志进行编译,或者使用其他替代方法:使用或根据方案进行记录。-fno-omit-frame-pointer--call-graph "dwarf"--call-graph "lbr"
1赞 David Faure 6/29/2022
如今,KDAB 的热点附带了 AppImage,使其非常易于使用。
1赞 user10306682 4/19/2020 #19

使用调试软件如何识别代码运行缓慢的地方?

只要想想你在运动时有一个障碍物,那么它会降低你的速度

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

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

还没有尝试过,但我听说过关于 google-perftools 的好消息。绝对值得一试。

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

它将生成一个名为 gmon.out 或 callgrind.out.x 的文件。然后,您可以使用 kcachegrind 或调试器工具读取此文件。它将为您提供事物的图形分析,并得出诸如哪些线路成本多少之类的结果。

我认为如此

评论

1赞 Basile Starynkevitch 9/9/2020
我实际上建议添加一些优化标志,例如编译g++ -O -pg -Wall your_prg.cpp
0赞 Muhammad Ali 11/11/2023 #20

有几种工具可用于此目的。每个都有其 拥有其他人所缺乏的独特功能。

格普罗夫

  • GNU 编译器集合 (GCC) 附带的分析工具。

  • 它还提供有关在程序的每个功能中花费的时间的信息。

  • 用法:

    g++ -pg -o my_program my_program.cpp
    ./my_program
    gprof ./my_program
    
  • Web => gprof - GNU工程

截图:

gprof

  1. 性能:
    • 描述: perf 是一个强大的性能分析工具,是 Linux 内核的一部分。它提供了广泛的功能,包括 CPU 性能计数器、跟踪点等。
    • 用法:
      perf record ./my_program
      perf report
      
    • Web => perf - Linux 手册页

截图:

perf

  1. 瓦尔格林德:
    • 描述:用于内存调试、内存泄漏检测和分析的编程工具。

    • 用法:

      valgrind --tool=callgrind ./my_program
      
    • Web Valgrind - Callgrind

截图:

valingard

  1. Google 性能工具 (gperftools):

    • 描述:gperftools 是 Google 的性能工具集合。它包括 CPU 和堆分析器(pprof 和 heap-checker)等。
    • 用法:
      LD_PRELOAD=/path/to/libtcmalloc.so CPUPROFILE=./my_program.prof ./my_program
      pprof --text ./my_program ./my_program.prof
      
    • Web gperftools - GitHub
  2. KCachegrind:

    • 描述:KCachegrind 是一个可视化前端,用于分析 Callgrind 生成的数据。它提供了一个交互式 GUI,用于分析分析分析信息。
    • 网络 [KCachegrind 手册]

(https://docs.kde.org/stable5/en/kdesdk/kcachegrind/index.html)

  1. Clang 的 AddressSanitizer 和 UndefinedBehaviorSanitizer:
    • 描述:这些清理程序可以帮助您发现代码中的内存问题和未定义的行为,这些问题可能会间接导致性能问题。
    • 网址 [AddressSanitizer]

(https://clang.llvm.org/docs/AddressSanitizer.html)

注意:

根据您的需要使用,希望对您有所帮助。