我正在寻找相关的性能指标来基准和优化我的C / C ++代码。例如,虚拟内存使用是一个简单但有效的指标,但我知道有些更专业并且有助于优化特定域:缓存命中/未命中,上下文切换等。
我相信这里有一个很好的地方可以列出一系列绩效指标,它们衡量的内容以及如何衡量它们,以帮助那些想要开始优化项目的人知道从哪里开始。
这就是大多数分析器默认测量/采样时间或核心时钟周期的原因。了解代码花费时间的地方是寻求加速的重要第一步。首先找出什么是缓慢的,然后找出它为什么慢。
您可以查找两种根本不同的加速比,时间将帮助您找到它们。
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延迟。但是这仍然很难在某处存储总数,甚至可能在更改元素时使用delta更新总数。 (但是,与整数不同,FP舍入误差可以累积。)如果您没有看到明显的算法改进,或者想要在进行更改之前了解更多信息,请检查CPU是否在任何情况下停滞不前,或者它是否有效地咀嚼编译器所做的所有工作。
每个时钟的指令(IPC)告诉您CPU是否接近其最大指令吞吐量。 (或者更准确地说,在x86上每个时钟发出融合域uops,因为例如一个rep movsb
指令是一个完整的大memcpy并解码为许多uops。而cmp / jcc从2个指令融合到1个uop,增加了IPC但是管道宽度仍然是固定的。)
按指令完成的工作也是一个因素,但不是你可以使用分析器测量的东西:如果你有专业知识,请查看编译器生成的asm,看看是否可以使用更少的指令进行相同的工作。如果编译器没有自动向量化,或者效率低下,那么根据问题,您可以通过手动向量化SIMD内在函数来完成每条指令的更多工作。或者通过调整C源来手动控制编译器发出更好的asm,以便以asm自然的方式计算事物。例如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,那么核心时钟周期可能更有用。)
多线程程序的大部分串行部分很难检测到;当其他线程被阻塞时,大多数工具都没有简单的方法来使用循环来查找线程。
但是,在函数中花费的时间并不是唯一的指标。通过触摸大量内存,函数可以使程序的其余部分变慢,从而导致从缓存中驱逐其他有用数据。所以这种效果是可能的。或者在某处有很多分支可能占用CPU的一些分支预测能力,导致其他地方出现更多的分支错过。
但请注意,在包含热点的函数可以拥有多个调用者的大型代码库中,仅仅找到CPU花费大量时间执行的地方并不是最有用的。例如在memcpy上花费大量时间并不意味着你需要加速memcpy,这意味着你需要找到哪个调用者正在调用memcpy。等等,备份调用树。
使用可以记录堆栈快照的分析器,或者只是在调试器中点击control-C并查看调用堆栈几次。如果某个功能通常出现在调用堆栈中,则会进行昂贵的调用。
相关:linux perf: how to interpret and find hotspots,特别是Mike Dunlavey的答案就是这一点。
但是,如果您发现某些工作的IPC非常低,您还没有想出如何避免,那么请务必重新考虑重新安排数据结构以获得更好的缓存,或避免分支错误预测。
或者,如果高IPC仍然需要很长时间,手动矢量化循环可以提供帮助,每条指令执行4次或更多次工作。
@PeterCordes答案总是很好。我只能添加自己的观点,大约40年来优化代码:
如果有时间被保存(有),那么花时间做一些不必要的事情,如果你知道它是什么就可以摆脱。
那是什么?既然你不知道它是什么,你也不知道需要多少时间,但确实需要时间。花费的时间越多,找到它就越有价值,找到它就越容易。假设需要30%的时间。这意味着随机时间快照有30%的机会显示它是什么。
我使用调试器和“暂停”功能获取调用堆栈的5-10个随机快照。
如果我看到它在一个以上的快照上做某事,并且那个事情可以更快或根本没有完成,我有一个大幅度的加速,保证。然后可以重复该过程以找到更多的加速,直到我达到递减收益。
关于这种方法的重要之处在于 - 没有“瓶颈”可以隐藏它。这使得它与剖析器不同,因为他们总结说,加速可以隐藏它们。