我正在寻找相关的性能指标来基准测试和优化我的C / C ++代码。例如,虚拟内存使用率是一个简单但有效的指标,但是我知道有些虚拟内存使用情况更为专业,可以帮助优化特定域:高速缓存命中/未命中,上下文切换等。
我相信这里是一个列出性能指标,衡量标准以及衡量标准的好地方,以帮助希望开始优化程序的人知道从何着手。
em>答案 0 :(得分:7)
这就是为什么大多数分析器默认使用测量/采样时间或核心时钟周期的原因。了解代码在何处花费时间是寻找加速的重要第一步。 首先找出什么慢,然后找出为什么慢。
您可以找到两种根本不同的加速方法,时间会帮助您找到它们。
算法上的改进:首先找到减少工作量的方法。这通常是最重要的一种,而Mike Dunlavey的回答集中于此。您绝对应该不忽略它。缓存重新计算速度很慢的结果非常值得,特别是如果速度太慢以至于从DRAM加载仍然更快。
使用可以更有效地解决实际CPU问题的数据结构/算法,介于这两种加速之间。 (例如,在实践中,链表通常比数组要慢,因为指针追随延迟是一个瓶颈,除非您最终过于频繁地复制大型数组...)
更有效地应用蛮力以更少的周期完成相同的工作。 (和/或对程序的其余部分更友好,缓存占用空间更小和/或分支更少,这会占用分支预测变量中的空间,等等)。
通常涉及更改数据布局以使其更易于缓存,和/或使用SIMD手动矢量化。或者以更聪明的方式这样做。或编写一个处理普通特殊情况的函数要比普通情况下的函数快。甚至还可以让编译器为您的C源代码制作更好的asm。
考虑对现代x86-64上的float
数组求和:从延迟绑定的标量添加到具有多个累加器的AVX SIMD,可以使速度提高8(每个向量元素)* 8(延迟/吞吐量)在Skylake上)= 64x(对于中等大小的阵列(仍在单个内核/线程上)),在理论上最好的情况下,您不会遇到另一个瓶颈(例如,如果您的数据在L1d缓存中不热,则为内存带宽) 。 Skylake vaddps
/ vaddss
具有4个周期的延迟,每2时钟= 0.5c的互惠吞吐量。 (https://agner.org/optimize/)。 Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables?,以获取更多有关多个累加器的信息以隐藏FP延迟。但是,与将总数存储在某个地方相比,这仍然很困难,甚至可能在更改元素时使用增量更新总数。 (但是,与整数不同,FP舍入误差会以这种方式累积。)
如果您没有看到明显的算法改进,或者想在进行更改之前了解更多信息,请检查CPU是否停滞在任何东西上,或者是否在效率上仔细考虑了编译器所做的所有工作。
每时钟指令数(IPC)告诉您CPU是否接近其最大指令吞吐量。 (或者更准确地说,x86上每个时钟发出的融合域uops,因为例如一个rep movsb
指令是一个很大的存储器,并解码为许多uops。cmp/ jcc从2条指令融合到1 uop,从而增加了IPC,但管道宽度仍然是固定的。)
每个指令完成的工作也是一个因素,但是您无法使用探查器进行测量:如果您具有专业知识,请查看编译器生成的asm,以查看是否有相同的工作使用更少的指令是可能的。如果编译器没有自动矢量化或效率低下,则根据问题,通过使用SIMD内部函数手动矢量化,您可能会为每条指令完成更多的工作。或者通过调整C源代码以使汇编语言自然而然地使编译器发出更好的汇编语言。例如What is the efficient way to count set bits at a position or lower?。并参见C++ code for testing the Collatz conjecture faster than hand-written assembly - why?
如果您发现IPC较低,请通过考虑诸如高速缓存未命中或分支未命中或较长的依赖链之类的可能性找出原因(当前端或内存没有瓶颈时,通常是IPC低的原因)。
或者您可能会发现它已经接近最佳地应用CPU的可用蛮力了(不太可能,但对于某些问题可能)。在这种情况下,您唯一的希望就是改进算法以减少工作量。
(CPU频率不是固定的,但是核心时钟周期是一个很好的代理。如果您的程序没有花时间等待I / O,那么核心时钟周期可能对 more 测量。)
多线程程序的大部分串行部分可能很难检测;大多数工具都没有简便的方法来在其他线程被阻塞时使用循环来查找线程。
花费在函数上的时间并不是 only 指示器。 通过触摸大量内存,该功能可使程序的其余部分变慢,从而导致从缓存中逐出其他有用数据。因此这种效果是可能的。或者某个地方有很多分支可能会占用CPU的某些分支预测能力,从而导致其他地方的分支未命中率更高。
但是请注意,在包含热点的函数可以有多个调用方的大型代码库中,简单地找出CPU在哪里花费大量时间执行并不是最有用的。例如在memcpy中花费大量时间并不意味着您需要加快memcpy的速度,而是意味着您需要找到哪个调用方经常调用memcpy。依此类推,备份呼叫树。
使用可记录堆栈快照的探查器,或仅在调试器中按Control-C并查看调用堆栈几次。如果某个函数通常出现在调用堆栈中,则表示正在进行昂贵的调用。
相关:linux perf: how to interpret and find hotspots,尤其是Mike Dunlavey的回答就说明了这一点。
但是,如果您发现某些工作的IPC值很低,您还没有想出如何避免的方法,那么一定要看看重新排列数据结构以更好地缓存或避免分支错误预测。
或者如果高级IPC仍然需要很长时间,则手动向量化循环可能会有所帮助,每条指令执行4倍或更多的工作。
答案 1 :(得分:1)
@PeterCordes的答案总是好的。我只能添加自己的观点,来自40年来优化代码的经验:
如果有时间要保存(有时间),那是花在做不必要的事情上,如果您知道它是什么,就可以摆脱它。
那是什么?由于您不知道它是什么,因此您也不知道需要多少时间,但是但是确实需要时间。花费的时间越多,找到的价值就越大,找到它就越容易。假设需要30%的时间。这意味着随机快照有30%的机会向您显示快照。
我使用调试器和“暂停”功能为调用堆栈拍摄5-10个随机快照。
如果我看到它在一个以上的快照上执行某项操作,并且可以更快地完成或根本不执行该操作,则可以确保得到了极大的提速。 然后可以重复该过程以查找更多加速比,直到我发现收益递减。
此方法的重要之处在于-“瓶颈”无法隐藏。这使它与探查器区分开来,探查器由于可以汇总,因此可以对它们进行隐藏。