手动 SIMD 代码的可负担性

Manual SIMD code affordability

我们是 运行 一个计算密集型项目,现在我们让编译器进行 SSE 优化。但是,我们不确定我们是否获得了代码的最佳性能。

我的问题是,据我所知,很宽泛,但我没有找到很多关于此的建议:编写手动 SIMD 代码是否负担得起,或者换句话说,值得付出努力?

在这里,负担能力是指对成本效益的粗略估计,例如 speedup / development_time,或在项目开发的背景下任何其他合理的衡量标准。

缩小范围:

您需要进行成本效益分析,例如如果你可以投入 X 个月的努力,花费 Y 美元,让你的代码速度提高 运行 N 倍,这意味着硬件成本的降低(例如 HPC 环境中的 CPU 更少),或者减少运行-时间,在某种程度上等同于成本效益,那么这是一个简单的算术练习。 (但请注意,存在一些无形的长期成本,例如 SIMD 优化代码往往更复杂、更容易出错、可移植性更差且更难维护。)

如果代码的性能关键部分(最热的 10%)是可向量化的,那么您可能会获得一个数量级的加速(双精度浮点数更少,更窄的数据类型例如16 位定点)。

请注意,这种优化并不总是只是将标量代码转换为 SIMD 代码的简单问题 - 您可能需要考虑您的数据结构和 cache/memory 访问模式。

非常同意 Paul R,只是想补充一点,IMO 在大多数情况下 intrinsics/asm 优化是不值得的。在大多数情况下,这些优化是营销驱动的,即我们在特定平台上榨取性能只是为了获得(在大多数情况下)更好的数字。

如今,仅仅重写 asm 中的 C/C++ 代码几乎不可能获得一个数量级的性能。在大多数情况下,正如 Paul 所指出的,这是 memory/cache 访问和 methods/algorithms(即并行化)的问题。

您应该尝试的第一件事是使用硬件性能计数器(使用免费 "perf" 工具或 Intel VTune)分析您的代码并了解真正的瓶颈。例如,计算过程中的内存访问实际上是最常见的瓶颈,而不是计算本身。因此,对此类代码进行手动矢量化无济于事,因为 CPU 无论如何都会在内存中停滞。

这样的分析总是值得的,因为您可以更好地理解您的代码和 CPU 体系结构。

接下来您应该尝试优化您的代码。有多种方法:优化数据结构、缓存友好的内存访问模式、更好的算法等。例如,您在结构中声明字段的顺序在某些情况下可能会对性能产生重大影响,因为您的结构可能有漏洞和占用两行缓存而不是一行。另一个例子是错误共享,当你在 CPUs 之间乒乓相同的缓存行时,简单的缓存对齐可能会给你一个数量级更好的性能。

这些优化总是值得付出努力,因为它们也会影响您的低级代码。

那你应该试着帮助你的编译器。例如,默认编译器 vectorize/unroll 一个内循环,但 vectorize/unroll 一个外循环可能更好。您可以使用 #pragma hints 执行此操作,有时值得付出努力。

您应该尝试的最后一件事是使用 intrinsics/asm 重写已经高度优化的 C/C++ 代码。这可能有一些原因,例如更好的指令交错(因此您的 CPU 管道总是很忙)或使用特殊的 CPU 指令(即用于加密)。合理的 intrinsics/asm 用法的实际数量可以忽略不计,并且它们始终取决于平台。

因此,如果没有关于您的 code/algorithms 的更多详细信息,很难猜测它对您的情况是否有意义,但我敢打赌不会。最好将精力花在分析和独立于平台的优化上。如果你真的需要那种计算能力,最好看看 OpenCL 或类似的框架。最后,投资更好的 CPUs:这种投资的效果是可预测和即时的。