C / C ++最相关的性能指标

问题描述 投票:4回答:2

我正在寻找相关的性能指标来基准和优化我的C / C ++代码。例如,虚拟内存使用是一个简单但有效的指标,但我知道有些更专业并且有助于优化特定域:缓存命中/未命中,上下文切换等。

我相信这里有一个很好的地方可以列出一系列绩效指标,它们衡量的内容以及如何衡量它们,以帮助那些想要开始优化项目的人知道从哪里开始。

performance optimization profiling cpu benchmarking
2个回答
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延迟。但是这仍然很难在某处存储总数,甚至可能在更改元素时使用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的答案就是这一点。


Algorithmic improvements to avoid doing work at all are often much more valuable than doing the same work more efficiently.

但是,如果您发现某些工作的IPC非常低,您还没有想出如何避免,那么请务必重新考虑重新安排数据结构以获得更好的缓存,或避免分支错误预测。

或者,如果高IPC仍然需要很长时间,手动矢量化循环可以提供帮助,每条指令执行4次或更多次工作。


1
投票

@PeterCordes答案总是很好。我只能添加自己的观点,大约40年来优化代码:

如果有时间被保存(有),那么花时间做一些不必要的事情,如果你知道它是什么就可以摆脱。

那是什么?既然你不知道它是什么,你也不知道需要多少时间,但确实需要时间。花费的时间越多,找到它就越有价值,找到它就越容易。假设需要30%的时间。这意味着随机时间快照有30%的机会显示它是什么。

我使用调试器和“暂停”功能获取调用堆栈的5-10个随机快照。

如果我看到它在一个以上的快照上做某事,并且那个事情可以更快或根本没有完成,我有一个大幅度的加速,保证。然后可以重复该过程以找到更多的加速,直到我达到递减收益。

关于这种方法的重要之处在于 - 没有“瓶颈”可以隐藏它。这使得它与剖析器不同,因为他们总结说,加速可以隐藏它们。

© www.soinside.com 2019 - 2024. All rights reserved.