如何选择AVX比较谓词变体

59
在高级矢量扩展(AVX)中,比较指令(例如_m256_cmp_ps)的最后一个参数是比较谓词。我对谓词的选择感到不知所措。它们似乎是类型、排序、信号的三元组。例如,_CMP_LE_OS表示“小于等于、有序、信号”。
首先,选择信号或非信号是否有性能原因?同样,有序还是无序比另一个更快?
那么,“非信号”是什么意思?我根本找不到这个东西的文档。有什么经验法则可以用来选择什么时候使用哪个?
以下是avxintrin.h中的谓词选择:
/* Compare */
#define _CMP_EQ_OQ    0x00 /* Equal (ordered, non-signaling)  */
#define _CMP_LT_OS    0x01 /* Less-than (ordered, signaling)  */
#define _CMP_LE_OS    0x02 /* Less-than-or-equal (ordered, signaling)  */
#define _CMP_UNORD_Q  0x03 /* Unordered (non-signaling)  */
#define _CMP_NEQ_UQ   0x04 /* Not-equal (unordered, non-signaling)  */
#define _CMP_NLT_US   0x05 /* Not-less-than (unordered, signaling)  */
#define _CMP_NLE_US   0x06 /* Not-less-than-or-equal (unordered, signaling)  */
#define _CMP_ORD_Q    0x07 /* Ordered (nonsignaling)   */
#define _CMP_EQ_UQ    0x08 /* Equal (unordered, non-signaling)  */
#define _CMP_NGE_US   0x09 /* Not-greater-than-or-equal (unord, signaling)  */
#define _CMP_NGT_US   0x0a /* Not-greater-than (unordered, signaling)  */
#define _CMP_FALSE_OQ 0x0b /* False (ordered, non-signaling)  */
#define _CMP_NEQ_OQ   0x0c /* Not-equal (ordered, non-signaling)  */
#define _CMP_GE_OS    0x0d /* Greater-than-or-equal (ordered, signaling)  */
#define _CMP_GT_OS    0x0e /* Greater-than (ordered, signaling)  */
#define _CMP_TRUE_UQ  0x0f /* True (unordered, non-signaling)  */
#define _CMP_EQ_OS    0x10 /* Equal (ordered, signaling)  */
#define _CMP_LT_OQ    0x11 /* Less-than (ordered, non-signaling)  */
#define _CMP_LE_OQ    0x12 /* Less-than-or-equal (ordered, non-signaling)  */
#define _CMP_UNORD_S  0x13 /* Unordered (signaling)  */
#define _CMP_NEQ_US   0x14 /* Not-equal (unordered, signaling)  */
#define _CMP_NLT_UQ   0x15 /* Not-less-than (unordered, non-signaling)  */
#define _CMP_NLE_UQ   0x16 /* Not-less-than-or-equal (unord, non-signaling)  */
#define _CMP_ORD_S    0x17 /* Ordered (signaling)  */
#define _CMP_EQ_US    0x18 /* Equal (unordered, signaling)  */
#define _CMP_NGE_UQ   0x19 /* Not-greater-than-or-equal (unord, non-sign)  */
#define _CMP_NGT_UQ   0x1a /* Not-greater-than (unordered, non-signaling)  */
#define _CMP_FALSE_OS 0x1b /* False (ordered, signaling)  */
#define _CMP_NEQ_OS   0x1c /* Not-equal (ordered, signaling)  */
#define _CMP_GE_OQ    0x1d /* Greater-than-or-equal (ordered, non-signaling)  */
#define _CMP_GT_OQ    0x1e /* Greater-than (ordered, non-signaling)  */
#define _CMP_TRUE_US  0x1f /* True (unordered, signaling)  */

如果你不会遇到NaN,那么这并不重要。 - Paul R
2个回答

41

Ordered与Unordered涉及到一个操作数包含NaN时比较是否为真(参见什么是有序/无序比较?)。信号(S)与非信号(Q for quiet?)将确定是否在操作数包含NaN时引发异常。

从性能的角度来看,这些应该都是相同的(当然假设没有引发任何异常)。如果您希望在存在NaN时收到警告,则需要使用信号。至于有序与无序,这取决于您处理NaN的方式。


1
信号传递实际上只意味着即使比较“安静”(正常)的NaN,FP“无效”标志也将被设置。要实际“警报”,您必须在MXCSR中取消屏蔽FP无效异常,或检查MXCSR粘性标志以查看自上次清除以来是否发生任何无效异常。Q与S的重点在于它允许您比较正常的NaN,而不会像除以零或inf-inf或sqrt(-1)那样对待它。 (SNaN不是自然发生的;如果掩盖异常,则这些其他无效操作会产生QNaN,默认设置。) - Peter Cordes

25
当任一操作数为NaN时,orderedunordered决定结果值。 Ordered比较返回false,对于NaN操作数。
_CMP_EQ_OQ在1.01.0之间的比较返回true(普通相等)。
_CMP_EQ_OQ在NaN1.0之间的比较返回false
_CMP_EQ_OQ在1.0NaN之间的比较返回false
_CMP_EQ_OQ在NaNNaN之间的比较返回falseUnordered比较对于NaN操作数返回true
_CMP_EQ_UQ在1.01.0之间的比较返回true(普通相等)。
_CMP_EQ_UQ在NaN1.0之间的比较返回true
_CMP_EQ_UQ在1.0NaN之间的比较返回true
_CMP_EQ_UQ在NaNNaN之间的比较返回trueSignallingnon-signalling之间的差异仅影响MXCSR的值。要观察效果,您需要清除MXCSR,执行一个或多个比较,然后从MXCSR中读取(感谢Peter Cordes澄清!)。
枚举值的顺序非常令人困惑。把它们放在表格中有助于理解...
比较 有序(非信号) 无序(非信号)
a < b _CMP_LT_OQ _CMP_NGE_UQ
a <= b _CMP_LE_OQ _CMP_NGT_UQ
a == b _CMP_EQ_OQ _CMP_EQ_UQ
a != b _CMP_NEQ_OQ _CMP_NEQ_UQ
a >= b _CMP_GE_OQ _CMP_NLT_UQ
a > b _CMP_GT_OQ _CMP_NLE_UQ
true _CMP_ORD_Q _CMP_TRUE_UQ(无用)
false _CMP_FALSE_OQ(无用) _CMP_UNORD_Q

使用MXCSR“信号”:

比较 有序(信号) 无序(信号)
a < b _CMP_LT_OS _CMP_NGE_US
a <= b _CMP_LE_OS _CMP_NGT_US
a == b _CMP_EQ_OS _CMP_EQ_US
a != b _CMP_NEQ_OS _CMP_NEQ_US
a >= b _CMP_GE_OS _CMP_NLT_US
a > b _CMP_GT_OS _CMP_NLE_US
true _CMP_ORD_S _CMP_TRUE_US(无用)
false _CMP_FALSE_OS(无用) _CMP_UNORD_S

枚举值的顺序可以解释为:

  • 前四个操作是规范的(EQLTLEUNORD)。请注意,如果0x000x03值是LE/UNORDUNORD/LE,则这四个规范操作可以被视为两个单独位的组合,但对于它们的实际顺序不可能。

  • 其余操作是前四个操作的转换。

  • 0x04位精确地反转结果值,这也有效地切换有序与无序。例如,LT_O变成了NLT_U,类似于GE,但请参见无序命名规则。

  • 0x08位切换有序与无序(而不改变其他任何内容)。

  • 同时设置0x040x08位会否定数值操作数的结果,同时保留NaN操作数的相同排序行为。例如,LT_O变成GE_O

  • 请注意,当比较是无序的(即0x040x08之一被设置)时,使用否定名称:NGE代替LTNGT代替LENLT代替GENLE代替GT;但EQNEQ需要定义有序和无序变体,因此这些名称仅在0x04否定转换下更改,而不是0x08有序性切换转换。

  • FALSE/TRUE大多是UNORD/ORD的无用的0x08转换,始终返回相同的值。例如,UNORD0x03)如果两个操作数都是数字,则返回false,如果其中一个是NaN,则返回true;添加0x08,我们得到FALSE0x0b),它对于NaN操作数具有切换行为,导致它对于两种情况都返回false

    有趣的事实:在AVX2之前,TRUE操作并不总是完全无用的。它是将YMM寄存器设置为所有1的最紧凑机制。有关详细信息,请参见https://godbolt.org/z/Yb5TjP(感谢Peter Cordes)。

  • 0x10位切换信令与否。请注意,在规范操作中,LELT是信令的,而EQUNORD则不是,因此设置0x10位会从LE/LT操作中删除信令,并将其添加到EQ/UNORD


1
我刚刚在另一个回答中发表了评论,关于信号与安静:默认情况下,FP异常被屏蔽,因此除非您检查MXCSR以查看自上次清除以来是否发生了任何屏蔽的“无效”异常,否则您将不会知道。或者取消屏蔽该异常。哦,问题是在问那个,我猜我应该回答一下。 - Peter Cordes
1
实际上,我的回答什么是有序/无序比较?已经涵盖了这个问题。不过可能需要进行编辑以提高清晰度。 - Peter Cordes
3
顺便说一下,"_CMP_TRUE_UQ" 不是完全没有用的,只有 98% - 它是使用 AVX1(而不是 AVX2)设置 YMM 寄存器为全 1 的最紧凑方式,所以你不需要使用 vpcmpeqd ymm15, ymm0, ymm0。一些编译器在针对 SandyBridge(使用 -mavx 而不是 -mavx2)时会使用它,如果你使用 _mm256_set1_epi8(-1)(对于 FP 向量来说很少需要,几乎是人为的/不现实的)。它确实有一个虚假的依赖关系,因此当可用时,当然会使用 AVX2 整数。是的,这个虚假谓词是无用的,vxorps xmm15, xmm0, xmm0 是将 ymm15 清零的更有效的方式。 - Peter Cordes
4
是的,Agner Fog的微架构指南证实了在所有CPU上(除了Silvermont),pcmpeq* 指令会打破依赖关系,尽管它需要执行单元来写入1(即使在Sandybridge系列上消除了异或归零)。通过注意到吞吐量甚至使用相同寄存器时优于1,可以验证这一点。参见Fastest way to set __m256 value to all ONE bits - Peter Cordes
@DaveDopson 哪一个符合IEEE 754标准?除了 a != b 之外,其他都是有序的,而对于 a != b 则是无序的,因为 NAN != NAN 的正确答案是 true。 - Soonts
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接