如何找到在Linux上运行的C++应用程序中运行缓慢的代码区域?
当前回答
较新的内核(例如最新的Ubuntu内核)附带了新的“perf”工具(apt-get-install-linux-tools)AKA perf_events。
这些都配有经典的采样分析器(手册页)以及很棒的时间图表!
重要的是,这些工具可以是系统评测,而不仅仅是进程评测-它们可以显示线程、进程和内核之间的交互,并让您了解进程之间的调度和I/O依赖关系。
其他回答
您可以使用loguru这样的日志框架,因为它包括时间戳和总运行时间,可以很好地用于分析:
使用调试软件如何识别代码运行缓慢的地方?
如果你在运动中遇到障碍物,那么它会降低你的速度
如不需要的重新分配的循环、缓冲区溢出、搜索、内存泄漏等操作消耗更多的执行能力,这将对代码的性能产生不利影响,在分析之前,请确保将-pg添加到编译中:
g++your_prg.cpp-pg或cc my_program.cpp-g-pg(根据编译器)
我还没有尝试过,但我听到了关于谷歌perftools的好消息。这绝对值得一试。
valgrind--tool=callgrind/(二进制文件)
它将生成一个名为gmon.out或callgrind.out.x的文件。然后可以使用kcachegrind或调试器工具来读取该文件。它会给你一个图形化的分析结果,比如哪一行花费多少。
我认为是这样
我假设你在使用GCC。标准的解决方案是使用gprof进行分析。
在分析之前,请确保将-pg添加到编译中:
cc -o myprog myprog.c utils.c -g -pg
我还没有尝试过,但我听到了关于谷歌perftools的好消息。这绝对值得一试。
这里有相关问题。
如果gprof不适合您,还有一些流行语:Valgrind、Intel VTune、Sun DTrace。
如果您的目标是使用探查器,请使用建议的探查器之一。
然而,如果您很匆忙,并且可以在调试器下手动中断程序,而程序主观上很慢,那么有一种简单的方法可以找到性能问题。
只需暂停几次,每次都查看调用堆栈。如果有一些代码浪费了一定百分比的时间,20%或50%或更多,这就是您在每个示例的操作中捕获它的概率。所以,这大概是你将看到的样本的百分比。不需要经过教育的猜测。如果你确实猜到了问题所在,这将证明或反驳它。
您可能有多个不同大小的性能问题。如果你清除了其中的任何一个,剩下的将占更大的百分比,并且在随后的传球中更容易被发现。这种放大效应,当在多个问题上叠加时,会导致真正的大规模加速因素。
注意:除非程序员自己使用过,否则他们倾向于怀疑这种技术。他们会说,分析程序提供了这些信息,但只有当他们对整个调用堆栈进行采样,然后让您检查一组随机样本时,这才是正确的。(总结是失去洞察力的地方。)调用图不能提供相同的信息,因为
他们不在教学层面进行总结,而且在递归的情况下,它们给出了令人困惑的摘要。
他们也会说它只适用于玩具程序,而实际上它适用于任何程序,而且它似乎更适用于大型程序,因为他们往往会发现更多问题。他们会说,它有时会发现不存在问题的东西,但只有当你看到一次时,这才是真的。如果您在多个样本上看到问题,则这是真实的。
P.S.如果有一种方法可以在某个时间点收集线程池的调用堆栈样本,这也可以在多线程程序上完成,就像在Java中一样。
P.P.S一般来说,软件中的抽象层越多,就越有可能发现这是导致性能问题的原因(以及加速的机会)。
补充:这可能并不明显,但堆栈采样技术在递归的情况下也同样适用。原因是,删除指令所节省的时间与包含指令的样本分数近似,而与样本中可能出现的次数无关。
我经常听到的另一个反对意见是:“它会随机停在某个地方,它会错过真正的问题”。这源于对真正的问题是什么有一个预先的概念。性能问题的一个关键特性是它们不符合预期。抽样告诉你有问题,你的第一反应是怀疑。这很自然,但你可以确定,如果它发现了问题,它是真实的,反之亦然。
补充:让我对它的工作原理做一个贝叶斯解释。假设有一些指令I(调用或其他)在调用堆栈上占用了时间的一小部分f(因此花费了那么多)。为了简单起见,假设我们不知道f是什么,但假设它是0.1、0.2、0.3、…0.9、1.0,并且这些可能性的先验概率都是0.1,所以所有这些成本都是先验的。
然后假设我们只取2个堆栈样本,我们在两个样本上都看到指令I,指定为观察值o=2/2。这为我们提供了I的频率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%。因此贝叶斯允许我们更新对I的可能成本的估计。如果数据量很小,它不能准确地告诉我们成本是多少,只是它足够大,值得修复。
另一种看待它的方式被称为继承规则。如果你把一枚硬币翻了两次,两次都是正面朝上,那么这能告诉你硬币的可能重量吗?令人尊敬的答案是,这是一个Beta分布,平均值(点击次数+1)/(尝试次数+2)=(2+1)/(2+2)=75%。
(关键是我们不止一次看到I。如果我们只看到一次,除了f>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%。
以下是检查测量值和检查堆栈样本之间的差异的即兴说明。瓶颈可能是一个像这样的大斑点,也可能是无数个小斑点,这没有什么区别。
测量是水平的;它告诉你特定时间的例行程序需要多少时间。采样是垂直的。如果有任何方法可以避免整个程序在那一刻所做的事情,并且如果您在第二个示例中看到了这一点,那么您就发现了瓶颈。这就是造成差异的原因——看到花费时间的全部原因,而不仅仅是花费多少。
使用具有以下选项的Valgrind:
valgrind --tool=callgrind ./(Your binary)
这将生成一个名为callgrind.out.x的文件。使用kcachegrind工具读取该文件。它会给你一个图形化的分析结果,比如哪一行花费多少。
推荐文章
- 在JS的Chrome CPU配置文件中,'self'和'total'之间的差异
- 在tmux中保持窗口名称固定
- 是否需要手动关闭ifstream?
- 如何生成一个核心转储在Linux上的分段错误?
- 在Python中如何在Linux和Windows中使用“/”(目录分隔符)?
- 为什么函数指针定义可以使用任意数量的&号或星号* ?
- 为什么我必须通过this指针访问模板基类成员?
- 什么是可重入函数?
- 什么是栈展开?
- 如何在内存中获取对象大小?
- 我如何读整个文件到性病::字符串在c++ ?
- 如何在c++中使用枚举
- 为什么512x512矩阵的转置比513x513矩阵的转置慢得多?
- 通过引用传递数组
- 为什么非const引用不能绑定到临时对象?