【发布时间】:2019-04-16 19:36:44
【问题描述】:
我正在寻找一个 SIMD 选项来加快比较,我找到了函数 __m128d _mm_cmpgt_sd (__m128d a, __m128d b)
显然它比较了较低的双精度,并将较高的双精度从a 复制到输出中。它在做什么是有道理的,但意义何在?这是为了解决什么问题?
【问题讨论】:
标签: x86 sse simd intrinsics
我正在寻找一个 SIMD 选项来加快比较,我找到了函数 __m128d _mm_cmpgt_sd (__m128d a, __m128d b)
显然它比较了较低的双精度,并将较高的双精度从a 复制到输出中。它在做什么是有道理的,但意义何在?这是为了解决什么问题?
【问题讨论】:
标签: x86 sse simd intrinsics
关键可能是在非常旧的硬件上,例如 Intel Pentium II 和 III,_mm_cmpgt_sd() 比 _mm_cmpgt_pd() 快。请参阅 Agner Fog 的instruction tables。这些处理器(PII 和 PIII)只有一个 64 位宽的浮点单元。 128 位宽的 SSE 指令在这些处理器上作为两个 64 位微操作执行。在较新的 CPU(例如 intel Core 2 (Merom) 和更新的 CPU)上,_pd 和 _ps 版本与 _sd 和 _ss 版本一样快。因此,如果您只需要比较单个元素而不关心结果的高 64 位,您可能更喜欢 _sd 和 _ss 版本。
此外,_mm_cmpgt_pd() 可能引发虚假浮点异常或性能下降,如果高位垃圾位意外包含NaN 或低于正常数,请参阅Peter Cordes’ answer。虽然在实践中,使用内部函数编程时应该很容易避免这种高位垃圾。
如果您想对您的代码进行矢量化,并且需要打包双重比较,请使用内部 _mm_cmpgt_pd(),而不是 _mm_cmpgt_sd()。
【讨论】:
cmppd 如果上部元素中的垃圾表示低于正常的double,则可以使用 FP 辅助。并且一些代码希望避免虚假的 FP 异常来比较可能的 NaN。
a 复制位。
DEST[MAXVL-1:64] (Unmodified)
cmpgtsd 或 cmpltsd 指令,即使对于“普通双精度数据类型上的常规旧比较运算符”也是如此。请参阅此Godbolt link。因为cmpltsd指令存在,所以_mm_cmpgt_sd自然也存在。此外,SIMD 代码通常包含小段不可矢量化的标量代码。在这种情况下,使用_mm_cmpgt_sd 和_mm_add_sd 等会很方便。Peter Cordes 已经提到addsd (_mm_add_sd) 作为水平求和的最后一步。
a 复制使这些功能更有意义....
cmpsd 是存在于 asm 中并在 XMM 寄存器上操作的指令,因此通过内在函数公开它会不不一致。
(几乎所有打包 FP 指令(除了 shuffles/blends)都有一个标量版本,因此 ISA 设计再次存在一致性参数;它只是相同操作码的额外前缀,可能需要更多晶体管来特殊 -操作码不支持标量版本的情况。)
你或设计内部 API 的人是否能想到一个合理的用例根本不重要。在此基础上遗漏一些东西是愚蠢的;当有人提出用例时,他们将不得不使用内联 asm 或编写可编译为更多指令的 C。
也许有一天有人会找到一个向量的用例,它的低半部分是掩码,高半部分是仍然有效的double。例如也许_mm_and_ps 回到输入以仅将低元素有条件地归零,而无需在高元素中进行打包比较来产生真值。
或者认为全1是NaN的位模式,全零是+0.0的位模式。
IIRC,cmppd 如果任何元素不正常(如果您没有在 MXCSR 中设置 DAZ 位),则会减慢速度。至少在设计 ISA 时存在的一些旧 CPU 上是这样。因此,对于 FP 比较,具有标量版本对于避免您不关心的元素的虚假 FP 辅助是(或曾经是)必不可少的。
也用于避免虚假的 FP 异常(或者如果它们被屏蔽,则设置异常标志),例如在任一向量的上部元素中存在 NaN 时。
@wim 还提出了一个很好的观点,即 Core2 之前的 Intel CPU 将 128 位 SIMD 指令解码为 2 个微指令,每个 64 位一半。因此,当您不需要高半结果时使用cmppd 总是会更慢,即使它不会出错。在没有 uop-cache 的 CPU 上,大量的多 uop 指令很容易成为前端解码器的瓶颈,因为只有一个解码器可以处理它们。
您通常不会将内部函数用于像 cmpsd 或 addsd 这样的 FP 标量指令,但它们存在以防您需要它们(例如,作为水平求和的最后一步)。更多情况下,在编译没有自动矢量化的标量代码时,您只需让编译器使用标量版本的指令即可。
通常对于标量比较,编译器会想要 EFLAGS 中的结果,因此将使用 ucomisd 而不是创建比较掩码,但对于无分支代码,掩码通常很有用,例如对于a < b ? c : 0.0 与cmpsd 和andpd。 (或者真的是andps,因为它更短并且与毫无意义的andpd 做同样的事情。)
【讨论】:
cmpsd 的任何惩罚。