_mm_cmpgt_sd 和其他类似方法有啥意义?
Posted
技术标签:
【中文标题】_mm_cmpgt_sd 和其他类似方法有啥意义?【英文标题】:What's the point of _mm_cmpgt_sd and other similar methods?_mm_cmpgt_sd 和其他类似方法有什么意义? 【发布时间】:2019-04-16 19:36:44 【问题描述】:我正在寻找一个 SIMD 选项来加快比较,我找到了函数 __m128d _mm_cmpgt_sd (__m128d a, __m128d b)
显然它比较了较低的双精度,并将较高的双精度从a
复制到输出中。它在做什么是有道理的,但意义何在?这是为了解决什么问题?
【问题讨论】:
【参考方案1】:关键可能是在非常旧的硬件上,例如 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()
。
【讨论】:
IIRC,cmppd
如果上部元素中的垃圾表示低于正常的double
,则可以使用 FP 辅助。并且一些代码希望避免虚假的 FP 异常来比较可能的 NaN。
但是,如果您不关心高位,那么为什么不对普通数据类型(例如 int、double 等)使用常规的旧比较运算符呢?因为您已经将一些数据(无论出于何种原因)加载到一个宽寄存器中?我想如果你只想要一个比较,更高的目标位将保留它们的当前值,而不是从 a
复制位。
@Jimbo:实际上最高目标位确实保留了它们的当前值,它们没有被修改。请参阅Intel's SDM 的第 750 页:CMPSD(128 位传统 SSE 版本)....DEST[MAXVL-1:64] (Unmodified)
@Jimbo:请注意,编译器可能会生成 cmpgtsd
或 cmpltsd
指令,即使对于“普通双精度数据类型上的常规旧比较运算符”也是如此。请参阅此Godbolt link。因为cmpltsd
指令存在,所以_mm_cmpgt_sd
自然也存在。此外,SIMD 代码通常包含小段不可矢量化的标量代码。在这种情况下,使用_mm_cmpgt_sd
和_mm_add_sd
等会很方便。Peter Cordes 已经提到addsd
(_mm_add_sd
) 作为水平求和的最后一步。
@wim Point 指出了有关英特尔 SDM 的信息。现在,如果您想要一个用于测试性能的大型 pdf,那么就有了!该文档中的信息似乎与我对英特尔内部指南网页上的伪代码的理解相矛盾。未修改的位而不是从a
复制使这些功能更有意义....【参考方案2】:
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
做同样的事情。)
【讨论】:
关于次正规和 FP 异常的优点。请注意,在现代 CPU 上,在输入低于正常值的情况下可能不会减速。根据Agner Fog 的说法,自桑迪桥以来,其中一些处罚已被取消。在 Sandy Bridge 上添加正常数和次正常数(第 131 页)不会受到处罚。我做了一些简单的实验,但在 Skylake 上也找不到cmpsd
的任何惩罚。以上是关于_mm_cmpgt_sd 和其他类似方法有啥意义?的主要内容,如果未能解决你的问题,请参考以下文章