如何在 x64 CPU 上快速计算 sincos?
Posted
技术标签:
【中文标题】如何在 x64 CPU 上快速计算 sincos?【英文标题】:How to compute sincos fast on a x64 CPU? 【发布时间】:2018-02-25 08:48:21 【问题描述】:这是针对熟悉 SSE/AVX 指令系列的用户以及熟悉其性能分析的用户提出的问题。我看到了很多不同的实现和方法,从older for SSE2 到更新的。网络上充斥着这样的链接。但就我个人而言,我在 sse 汇编分析方面经验并不丰富。有些人指出了微指令、缓存,这需要一些低级知识。所以我要求一个提示和你的个人经历。如果您有时间进行一些比较,关于“什么是最快的”以及为什么,您查看了哪些方法。实现可能不是那么精确,10-16 位的单 FP 精度就足够了。越多越好,但不影响速度。
PS。为了避免元洪水,我可以用细节准确地描述任务:
给定标量参数 x(以弧度为单位),它在 xmm 寄存器中传递(根据 x64 快速调用约定)。 写一个带有签名__m128 sincos(float x)
的函数;返回其 sin(x) 和 cos(x) 值的近似值。
返回值应在一个 xmm 寄存器内,并以尽可能快的方式计算,以满足 10 位精度要求。
参数可以是任何实数(但不能是nan
、inf
,等等)。如果方法需要参数规范化,则其高性能实现(fmod())也是主题。但问题不在于处理特殊的 FP 案例。
这可能是重复的,但我没有在这里找到类似的问题,所以请指出我,如果已经有一个。
【问题讨论】:
函数签名不可能是最优的;您需要为一个输入向量返回 2 个单独的向量。只有一个输出向量,您需要洗牌,并且只有 2 对 sin / cos 结果的空间来自 4float
元素。大多数使用结果的代码都必须将它们分开。在 asm 中,显然只需返回 xmm0
和 xmm1
。使用 C 内在函数,要么返回 2 __m128
的结构,要么(可能更好)通过引用获取输出 arg。
@Peter Cordes 案例是 2D 游戏,通常最好有单位向量(方向向量),从角度构造,以进一步将其传递给几何算法,如运动积分、碰撞检测、射线投射等等。另一方面,如果开发者真的不需要 shuffle 指令,则可以删除它。
在该用例中,您肯定希望每个单位向量的 x 和 y 坐标位于单独的 SIMD 向量中,因为您将在垂直操作中使用它们。将 2D 或 3D 向量的多个分量放入单个 SIMD 向量中是一个典型的错误示例。请参阅deplinenoise.wordpress.com/2015/03/06/… 以获得详细的 SIMD 简介,它清楚地说明了这一点。 (另请参阅***.com/tags/sse/info 中的其他链接)
@Peter Cordes,是的,经过几天的研究和开发,现在我明白了这一点并看到了不同之处。我最终重构了几乎所有代码以避免分支并并行执行计算。这揭示了一些体系结构的弱点,并且需要额外的存储来存储中间结果,这在以前的串行代码中并不明显。现在它更像是管道输送机,将单独的数据阵列从一个阶段传递到另一个阶段。出色的表现!
有了一个好的系统(比如 gcc 和最近的 glibc),你不需要做任何特别的事情(也许传递一些像 -ffast-math 这样的标志)。编译器会注意到你是否同时使用 sin 和 cos 并将它们与 sincos 一起计算,如果它在循环中使用,它会自动向量化并调用 sincos 的向量化版本(如果你使用 glibc,则来自 libmvec)。跨度>
【参考方案1】:
感谢 Giovanni Garberoglio,我发现了 Julien Pommier implementations 的现代修订版,移植到 zlib 下的 AVX/AVX2:
http://software-lisc.fbk.eu/avx_mathfun/
它的工作速度非常快,在 i7 3770k 的单核上每秒迭代 80-90M,每次迭代提供 8 个正弦和 8 个余弦。与 ~15Mhz 相比,如果我每次迭代调用 8 个 sinf() 和 8 个 cosf()(来自 msvc2017 x64 库的函数,带有 avx 编译器设置)
统一更新:
还有一个出色的 FastTrigo 代码示例,其中 FT::sincos()
函数比 Julien Pommier 的实现快 20%。而他的FT::sincos()
正好提供了 10 位的保证精度。
【讨论】:
以上是关于如何在 x64 CPU 上快速计算 sincos?的主要内容,如果未能解决你的问题,请参考以下文章