_mm_cmpgt_sd 和其他类似方法有什么意义?

What's the point of _mm_cmpgt_sd and other similar methods?

我正在寻找一个 SIMD 选项来加速比较,我找到了函数 __m128d _mm_cmpgt_sd (__m128d a, __m128d b)

显然它比较了较低的双精度,并将 a 中的较高双精度复制到输出中。它在做什么是有道理的,但有什么意义呢?这是要解决什么问题?

重点可能是在非常老的硬件上,例如 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) 和更新版本)上,_pd_ps 版本与 _sd_ss 版本一样快。因此,如果您只需要比较单个元素而不关心结果的高 64 位,您可能更喜欢 _sd_ss 版本。

此外,_mm_cmpgt_pd() 可能引发伪浮点异常或性能下降,如果高位垃圾位意外包含 NaN 或次正规数,请参阅 。尽管在实践中,使用内在函数编程时应该很容易避免此类高位垃圾位。

如果您想向量化您的代码,并且需要压缩双重比较,请使用内部 _mm_cmpgt_pd(),而不是 _mm_cmpgt_sd()

cmpsd 是存在于 asm 中并在 XMM 寄存器上运行的指令,因此 不一致 而不是 通过内部函数公开它。

(几乎所有 packed-FP 指令(shuffles/blends 除外)都有一个标量版本,因此 ISA 设计再次存在一致性论点;它只是相同操作码的额外前缀,可能需要更多晶体管到操作码支持标量版本的特殊情况。)

您或设计内在函数的人 API 是否能想到一个合理的用例根本不是重点。 这样做是愚蠢的在此基础上将事情排除在外;当有人提出用例时,他们将不得不使用内联 asm 或编写编译为更多指令的 C。

也许有一天有人会找到一个向量的用例,其中掩码作为下半部分,而仍然有效的 double 在上半部分。例如可能 _mm_and_ps 返回到输入以有条件地仅将低元素置零 而无需在高元素中进行打包比较以产生 true。

或者考虑全一是 NaN 的位模式,全零是 +0.0 的位模式。


IIRC,cmppd 如果任何元素不正常(如果您没有在 MXCSR 中设置 DAZ 位),则会减慢速度。至少在设计 ISA 时存在的一些旧 CPU 上是这样。因此,对于 FP 比较,拥有标量版本对于(或曾经)避免对您不关心的元素进行虚假 FP 辅助至关重要。

也为了避免伪造的 FP 异常(或者如果它们被屏蔽则设置异常标志),就像任一向量的上部元素中有 NaN 一样。

@wim 还提出了一个很好的观点,即 Core2 之前的英特尔 CPU 将 128 位 SIMD 指令解码为 2 微指令,每个 64 位一半。因此,当您不需要高半结果时使用 cmppd 总是会更慢,即使它不会出错。许多多 uop 指令很容易在没有 uop 缓存的 CPU 上成为前端解码器的瓶颈,因为只有一个解码器可以处理它们。


您通常不会将内部函数用于 FP 标量指令,例如 cmpsdaddsd,但它们存在以防您需要它们(例如,作为水平求和中的最后一步)。更多情况下,在编译没有自动矢量化的标量代码时,您只需让编译器使用指令的标量版本。

通常对于标量比较,编译器会希望在 EFLAGS 中得到结果,因此将使用 ucomisd 而不是创建比较掩码,但对于无分支代码,掩码通常很有用,例如a < b ? c : 0.0cmpsdandpd。 (或者真的 andps 因为它更短并且与毫无意义的 andpd 做同样的事情。)