我有一个C++应用程序,运行在Linux上,我在优化过程中。如何确定代码的哪些部分运行缓慢
如果您的目标是使用探查器,请使用建议的探查器之一
但是,如果您很匆忙,并且可以在调试器下手动中断程序,而程序主观上运行缓慢,那么有一种简单的方法可以发现性能问题
只需将其暂停几次,每次都查看调用堆栈。如果有一些代码浪费了一定比例的时间,20%或50%或其他什么,这就是您在每个示例的act中捕获它的概率。所以,这大概是你将看到它的样本的百分比。不需要经过教育的猜测。如果你确实对问题是什么有一个猜测,这将证明或反驳它
您可能有多个不同大小的性能问题。如果你清除了其中的任何一个,剩下的将占据更大的百分比,并更容易发现,在随后的通行证。这种放大效应在多个问题上复合时,会导致真正巨大的加速因子
警告:程序员倾向于怀疑这种技术,除非他们自己使用过。他们会说分析器提供了这些信息,但只有当他们对整个调用堆栈进行采样,然后让您检查一组随机样本时,这才是真的。(总结是失去洞察力的地方。)调用图不会提供相同的信息,因为
- 他们没有在指导级别进行总结,并且
- 他们在递归的情况下给出了令人困惑的摘要
他们也会说它只在玩具程序上有效,而实际上它在任何程序上都有效,而且在更大的程序上似乎效果更好,因为他们往往有更多的问题要找。他们会说,它有时会发现一些不是问题的东西,但只有当你看到某个东西一次时,这才是真的。如果您在多个样本上看到问题,那么它是真实的
p.S.如果有一种方法可以在某个时间点收集线程池的调用堆栈样本,那么这也可以在多线程程序上完成,就像在Java中一样
p.p.S大致来说,软件中的抽象层越多,就越有可能发现这是性能问题的原因(以及加速的机会)
添加了:这可能不明显,但堆栈采样技术在递归中同样有效。原因是,删除指令所节省的时间近似于包含该指令的样本的分数,而不管它在样本中出现的次数
我经常听到的另一个反对意见是:“它会在某个地方随机停止,并且会忽略真正的问题。”。
这来自于对真正的问题有一个先验的概念。
性能问题的一个关键特性是它们不符合预期。
抽样告诉你有些东西是个问题,你的第一反应是不相信。
这是很自然的,但你可以肯定,如果它发现了一个问题,它是真实的,反之亦然
新增:让我对其工作原理做一个贝叶斯解释。假设有一些指令I(调用或其他)在调用堆栈上,占时间的一小部分f(因此成本很高)。为了简单起见,假设我们不知道什么是f,但假设它是0.1、0.2、0.3。。。0.9,1.0,这些可能性的先验概率都是0.1,所以所有这些成本在先验上都是相同的
然后假设我们只取两个堆栈样本,在两个样本上都看到指令I,指定为观测o=2/2。这为我们提供了对I的频率f的新估计,如下所示:
之前
P(f=x)xp(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是便宜的。然后我们得到:
之前
P(f=x)xp(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=10,f=0.3,即3+/-1.4样本。)
添加了:直观地感受测量和随机堆栈采样之间的区别:
现在有分析器可以对堆栈进行采样,即使是在墙上的时钟时间,但产生的是测量值(或热路径或热点,从中可以很容易地隐藏“瓶颈”)。他们没有向你展示(而且他们很容易做到)的是实际样品本身。如果您的目标是找到瓶颈,那么您需要看到的瓶颈数量平均为2除以所需时间的分数。
因此,如果需要30%的时间,平均2/.3=6.7个样本会显示,20个样本显示的几率为99.2%
这里是一个现成的例子,说明了检查测量值和检查堆栈样本之间的区别。
瓶颈可能是一个像这样的大斑点,也可能是许多小斑点,这没有什么区别
测量是水平的;它告诉你特定时间的例行程序需要多少时间。
抽样是垂直的。
如果有任何方法可以避免整个程序此时正在执行的操作,并且如果您在第二个示例中看到它,那么您已经找到了瓶颈。
这就是区别所在——看看花时间的全部原因,而不仅仅是花了多少时间