用于浮点相等比较的SIMD指令(NaN == NaN)
哪些指令可用于比较由4 * 32位浮点值组成的两个128位向量?
是否有一条指令将双方的NaN值视为相等? 如果不是,提供反身性(即NaN等于NaN)的解决方法的性能影响有多大?
我听说,与IEEE语义相比,确保自反性会产生显着的性能影响,NaN并不等于自己,我想知道这种影响是否会大。
我知道您在处理浮点值时通常要使用epsilon比较而不是确切的质量。 但是这个问题是关于精确的相等比较,你可以用它来消除哈希集中的重复值。
要求
+0
和-0
必须相等。 NaN
必须与自身相等。 true
如果所有四个浮动元件在两种载体相同,并且如果至少一个元件不同假。 其中true
由标量整数1
, false
由0
。 测试用例
(NaN, 0, 0, 0) == (NaN, 0, 0, 0) // for all representations of NaN
(-0, 0, 0, 0) == (+0, 0, 0, 0) // equal despite different bitwise representations
(1, 0, 0, 0) == (1, 0, 0, 0)
(0, 0, 0, 0) != (1, 0, 0, 0) // at least one different element => not equal
(1, 0, 0, 0) != (0, 0, 0, 0)
我的想法实现这一点
我认为可以将两个NotLessThan
比较( CMPNLTPS
?)结合使用and
实现所需的结果。 AllTrue(!(x < y) and !(y < x))
或AllFalse((x < y) or (y > x)
的汇编程序等价物。
背景
这个问题的背景是微软计划向.NET添加一个Vector类型。 我在哪里争论反射式.Equals
方法,并且需要更清楚地了解这种反射的性能影响等于IEEE等于多大。 请参阅Vector<float>.Equals
是否自反或应遵循IEEE 754语义? 对程序员而言,这是长篇故事。
即使在AVX VCMPPS可用时(由于它的谓词选择大大增强),它的效率也不如IEEE比较。 您必须至少进行两次比较并合并结果。 尽管如此,这还不算太坏。
不同的NaN编码是不相等的:有效地增加2个insns(增加2个uops)。 没有AVX:除此之外还有一个额外的movaps
。
不同的NaN编码是相等的:有效地增加4个insns(增加4个uops)。 没有AVX:两个额外的movaps
insn
IEEE比较分支是3 cmpeqps
: cmpeqps
/ movmskps
/ test-and-branch。 英特尔和AMD将测试和分支宏观融合到单个uop / m-op中。
随着AVX512:逐位男可能只是一个额外的指令,因为法向量比较和部门可能使用vcmpEQ_OQps
/ ktest same,same
/ jcc
,所以组合两种不同面具的REG是免费的(只是改变参数传递给ktest
)。 唯一的代价是额外的vpcmpeqd k2, xmm0,xmm1
。
AVX512 any-NaN只是两个额外的指令(2x VFPCLASSPS
,第二个使用第一个作为零掩码的结果,见下文)。 再次,然后ktest
用两个不同的参数设置标志。
我迄今为止最好的想法是: ieee_equal || bitwise_equal
ieee_equal || bitwise_equal
如果我们放弃考虑彼此相等的不同NaN编码:
+0 == -0
情况。 有没有情况下,无论是比较给人一种假阳性(因为ieee_equal
是假的,当一个操作数为NaN:我们只想要平等,不等于有或无序的AVX vcmpps
。提供了两个选项,而SSE只是提供了一个简单的等于操作)
我们想知道什么时候所有的元素都是平等的,所以我们应该从倒数比较开始。 检查至少一个非零元素比检查所有非零元素更容易。 (即水平的AND很难,水平的OR很容易( pmovmskb
/ test
或ptest
)。相反的比较是免费的( jnz
而不是jz
)。)这与Paul R使用的技巧是一样的。
; inputs in xmm0, xmm1
movaps xmm2, xmm0 ; unneeded with 3-operand AVX instructions
cmpneqps xmm2, xmm1 ; 0:A and B are ordered and equal. -1:not ieee_equal. predicate=NEQ_UQ in VEX encoding expanded notation
pcmpeqd xmm0, xmm1 ; -1:bitwise equal 0:otherwise
; xmm0 xmm2
; 0 0 -> equal (ieee_equal only)
; 0 -1 -> unequal (neither)
; -1 0 -> equal (bitwise equal and ieee_equal)
; -1 -1 -> equal (bitwise equal only: only happens when both are NaN)
andnps xmm0, xmm2 ; NOT(xmm0) AND xmm2
; xmm0 elements are -1 where (not bitwise equal) AND (not IEEE equal).
; xmm0 all-zero iff every element was bitwise or IEEE equal, or both
movmskps eax, xmm0
test eax, eax ; it's too bad movmsk doesn't set EFLAGS according to the result
jz no_differences
对于双精度, ...PS
和pcmpeqQ
工作原理是一样的。
如果不相等的代码继续查找哪个元素不相等,则对movmskps
结果进行位扫描会得出第一个差异的位置。
使用SSE4.1 PTEST
您可以使用andnps
movmskps
替换andnps
/ movmskps
/ test-and-branch:
ptest xmm0, xmm2 ; CF = 0 == (NOT(xmm0) AND xmm2).
jc no_differences
我预计这是大多数人第一次看到PTEST
的CF
结果对任何事物PTEST
用。 :)
在Intel和AMD CPU上((2ptest + 1jcc)vs(pandn + movmsk + fused-test&branch))仍然是三个微指令,但指令较少。 如果你打算使用setcc
或者cmovcc
而不是jcc
,那么效率会setcc
,因为它们不能与test
宏观融合。
这样总共有6个uops(5个AVX)用于自反比较分支,而3个uops用于IEEE比较分支 。 ( cmpeqps
/ movmskps
/ test-and-branch。)
PTEST
在AMD Bulldozer家族CPU(Steamroller上的14c)上有非常高的延迟。 它们具有由两个整数核心共享的一组矢量执行单元。 (这是他们超线程的替代方案。)这增加了可以检测到分支错误预测或数据依赖链延迟( cmovcc
/ setcc
)的时间。
当0==(xmm0 AND xmm2)
时,PTEST设置ZF
:如果没有元素都是bitwise_equal
和IEEE(neq或无序),则设置ZF
。 即如果任何元素是bitwise_equal
而ZF是未设置的,同时也是!ieee_equal
。 这只有在一对元素包含按位相等的NaN
时才会发生(但在其他元素不相等时可能会发生)。
movaps xmm2, xmm0
cmpneqps xmm2, xmm1 ; 0:A and B are ordered and equal.
pcmpeqd xmm0, xmm1 ; -1:bitwise equal
ptest xmm0, xmm2
jc equal_reflexive ; other cases
...
equal_reflexive:
setnz dl ; set if at least one both-nan element
没有条件测试CF=1
和任何关于ZF
。 ja
测试CF=0 and ZF=1
。 你不可能只想测试它,所以把jnz
放在jc
分支目标中就可以正常工作。 (如果你只想测试equal_reflexive
和at_least_one_nan
,不同的设置可能会适当地设置标志)。
考虑到所有NaNs相等,即使不是按位相等:
这与Paul R的答案是一样的,但有一个错误修正(使用AND而不是OR将NaN检查与IEEE检查相结合)。
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpordps xmm2, xmm2 ; find NaNs in A. (0: NaN. -1: anything else). Same as cmpeqps since src and dest are the same.
movaps xmm3, xmm1
cmpordps xmm3, xmm3 ; find NaNs in B
orps xmm2, xmm3 ; 0:A and B are both NaN. -1:anything else
cmpneqps xmm0, xmm1 ; 0:IEEE equal (and ordered). -1:unequal or unordered
; xmm0 AND xmm2 is zero where elements are IEEE equal, or both NaN
; xmm0 xmm2
; 0 0 -> equal (ieee_equal and both NaN (impossible))
; 0 -1 -> equal (ieee_equal)
; -1 0 -> equal (both NaN)
; -1 -1 -> unequal (neither equality condition)
ptest xmm0, xmm2 ; ZF= 0 == (xmm0 AND xmm2). Set if no differences in any element
jz equal_reflexive
; else at least one element was unequal
; alternative to PTEST: andps xmm0, xmm2 / movmskps / test / jz
所以在这种情况下, PTEST
我们不需要PTEST
的CF
结果。 我们在使用PCMPEQD
时会PCMPEQD
,因为它没有反转( cmpunordps
的方式有cmpordps
)。
用于Intel SnB系列CPU的9个融合域uops。 (7 AVX:使用非破坏性3操作数指令,以避免movaps
)。但是,预SKYLAKE微架构SNB-系列CPU只能运行cmpps
在P1,所以在这个瓶颈如果吞吐量是一个问题FP-添加单元。 Skylake在p0 / p1上运行cmpps
。
andps
比更短的编码pand
,和英特尔CPU从Nehalem处理器到Broadwell微架构只能在PORT5运行它。 为防止它从周围的FP代码盗取p0或p1周期,这可能是需要的。 否则, pandn
可能是更好的选择。 在AMD BD系列中, andnps
无论如何都在ivec域中运行,所以您不要避免int和FP向量之间的旁路延迟(如果您使用movmskps
而不是ptest
,那么在此版本中只使用cmpps
,而不是pcmpeqd
)。 另外请注意,此处选择指令排序是为了便于人员阅读。 之前在ANDPS
之前将FP比较(A,B)可能会帮助CPU更快地开始该周期。
如果一个操作数被重用,应该可以重用自己的NaN查找结果。 新的操作数仍然需要自我NaN检查,并与重用的操作数进行比较,所以我们只保存一个movaps
/ cmpps
。
如果向量在内存中,至少其中的一个需要使用单独的加载insn加载。 另一个只能从内存中引用两次。 这很糟糕,如果它没有对齐或寻址模式不能微型融合,但可能有用。 如果vcmpps
的一个操作数是一个已知没有NaN的向量(例如一个归零寄存器), vcmpunord_qps xmm2, xmm15, [rsi]
将在[rsi]
找到NaN。
如果我们不想使用PTEST
,可以通过使用相反的比较来获得相同的结果,但将它们与相反的逻辑运算符(AND与OR)结合使用。
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpunordps xmm2, xmm2 ; find NaNs in A (-1:NaN 0:anything else)
movaps xmm3, xmm1
cmpunordps xmm3, xmm3 ; find NaNs in B
andps xmm2, xmm3 ; xmm2 = (-1:both NaN 0:anything else)
; now in the same boat as before: xmm2 is set for elements we want to consider equal, even though they're not IEEE equal
cmpeqps xmm0, xmm1 ; -1:ieee_equal 0:unordered or unequal
; xmm0 xmm2
; -1 0 -> equal (ieee_equal)
; -1 -1 -> equal (ieee_equal and both NaN (impossible))
; 0 0 -> unequal (neither)
; 0 -1 -> equal (both NaN)
orps xmm0, xmm2 ; 0: unequal. -1:reflexive_equal
movmskps eax, xmm0
test eax, eax
jnz equal_reflexive
其他想法:未完成的,不可行的,破碎的或者比上述更差的
真正比较的全部结果是NaN
的编码。 (试一下,也许我们可以避免使用POR
或PAND
来cmpps
在每个操作数上合并来自cmpps
结果?
; inputs in A:xmm0 B:xmm1
movaps xmm2, xmm0
cmpordps xmm2, xmm2 ; find NaNs in A. (0: NaN. -1: anything else). Same as cmpeqps since src and dest are the same.
; cmpunordps wouldn't be useful: NaN stays NaN, while other values are zeroed. (This could be useful if ORPS didn't exist)
; integer -1 (all-ones) is a NaN encoding, but all-zeros is 0.0
cmpunordps xmm2, xmm1
; A:NaN B:0 -> 0 unord 0 -> false
; A:0 B:NaN -> NaN unord NaN -> true
; A:0 B:0 -> NaN unord 0 -> true
; A:NaN B:NaN -> 0 unord NaN -> true
; Desired: 0 where A and B are both NaN.
cmpordps xmm2, xmm1
只是翻转每个案例的最终结果,“奇数人出”仍然在第一行。
如果两个输入都被反转(NaN - >非NaN,反之亦然),我们只能得到我们想要的结果(如果A和B都是NaN,则为true)。 这意味着我们可以使用这个想法cmpordps
作为替代pand
做后cmpordps self,self
在A和B,这是没有用的:即使我们有AVX但不AVX2,我们可以使用vandps
和vandnps
(和vmovmskps
因为vptest
只是AVX2)。 按位布尔值仅为单周期延迟,并且不会捆绑已成为此代码瓶颈的vector-FP-add执行端口。
VFIXUPIMMPS
我花了一段时间与手动grokking其操作。
如果源元素是NaN,它可以修改目标元素,但是不能以任何关于dest元素为条件。
我希望能想到一种方法到vcmpneqps
,然后修正这个结果,每个源操作数一次( vcmpps
组合3 vcmpps
指令结果的vcmpps
指令)。 我现在确信这是不可能的,因为知道一个操作数是NaN本身不足以改变IEEE_equal(A,B)
结果。
我认为我们可以使用vfixupimmps
的唯一方法是分别检测每个源操作数中的NaN,如vcmpunord_qps
但更糟糕。 或者作为一个非常愚蠢的替代andps
,在前面比较的掩码结果中检测到0或全部(NaN)。
AVX512屏蔽寄存器
使用AVX512屏蔽寄存器可以帮助结合比较结果。 大多数AVX512比较指令将结果放入一个掩码寄存器,而不是一个矢量区中的掩码矢量,所以如果我们想要以512b块进行操作,我们实际上必须这样做。
VFPCLASSPS k2 {k1}, xmm2, imm8
写入一个屏蔽寄存器,可选地由不同的屏蔽寄存器屏蔽。 通过只设置imm8的QNaN和SNaN位,我们可以得到一个向量中NaN位置的掩码。 通过设置所有其他位,我们可以得到相反的结果。
通过使用A中的掩码作为B上的vfpclassps
的零掩码,我们可以只用2条指令找到两个NaN位置,而不是通常的cmp / cmp / combine。 因此,我们保存or
或andn
指令。 顺便提一句,我想知道为什么没有OR-NOT操作。 可能它的出现次数比AND-NOT少,或者他们不想在指令集中使用porn
。
yasm和nasm都不能组装这个,所以我甚至不确定我的语法是否正确!
; I think this works
; 0x81 = CLASS_QNAN|CLASS_SNAN (first and last bits of the imm8)
VFPCLASSPS k1, zmm0, 0x81 ; k1 = 1:NaN in A. 0:non-NaN
VFPCLASSPS k2{k1}, zmm1, 0x81 ; k2 = 1:NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
;; so k2 is like the bitwise-equal result from pcmpeqd: it's an override for ieee_equal
vcmpNEQ_UQps k3, zmm0, zmm1
;; k3= 0 only where IEEE equal (because of cmpneqps normal operation)
; k2 k3 ; same logic table as the pcmpeqd bitwise-NaN version
; 0 0 -> equal (ieee equal)
; 0 1 -> unequal (neither)
; 1 0 -> equal (ieee equal and both-NaN (impossible))
; 1 1 -> equal (both NaN)
; not(k2) AND k3 is true only when the element is unequal (bitwise and ieee)
KTESTW k2, k3 ; same as PTEST: set CF from 0 == (NOT(k2) AND k2)
jc .reflexive_equal
我们可以重复使用相同的屏蔽寄存器作为第二个vfpclassps
insn的零掩码和目标寄存器,但是我使用了不同的寄存器以防我在注释中区分它们。 该代码至少需要两个掩码寄存器,但不需要额外的向量寄存器。 我们也可以使用k0
而不是k3
作为vcmpps
的目的地,因为我们不需要使用它作为谓词,只能作为dest和src。 ( k0
是不能用作谓词的寄存器,因为该编码手段意味着“无掩码”。)
我不确定我们能否为每个元素创建一个具有reflexive_equal
结果的单个掩码,而不需要k...
指令在某个点结合两个掩码(例如kandnw
而不是ktestw
)。 掩码只能作为零掩码,而不能将结果强制为一个掩码,所以将vfpclassps
结果组合起来只能用作AND。 所以我认为我们坚持使用1-both-both-NaN,这是将它用作vcmpps的vcmpps
的错误vcmpps
。 首先执行vcmpps
,然后将掩码寄存器用作vfpclassps
目标和谓词,也无济于事。 合并掩码而不是零掩码可以做到这一点,但在写入掩码寄存器时不可用。
;;; Demonstrate that it's hard (probably impossible) to avoid using any k... instructions
vcmpneq_uqps k1, zmm0, zmm1 ; 0:ieee equal 1:unequal or unordered
vfpclassps k2{k1}, zmm0, 0x81 ; 0:ieee equal or A is NaN. 1:unequal
vfpclassps k2{k2}, zmm1, 0x81 ; 0:ieee equal | A is NaN | B is NaN. 1:unequal
;; This is just a slow way to do vcmpneq_Oqps: ordered and unequal.
vfpclassps k3{k1}, zmm0, ~0x81 ; 0:ieee equal or A is not NaN. 1:unequal and A is NaN
vfpclassps k3{k3}, zmm1, ~0x81 ; 0:ieee equal | A is not NaN | B is not NaN. 1:unequal & A is NaN & B is NaN
;; nope, mixes the conditions the wrong way.
;; The bits that remain set don't have any information from vcmpneqps left: both-NaN is always ieee-unequal.
如果ktest
最终成为像ptest
这样的2个ktest
,并且不能进行宏观融合,那么kmov eax, k2
/ test-and-branch可能会比ktest k1,k2
/ jcc便宜。 希望它只会是一个uop,因为掩码寄存器更像是整数寄存器,并且可以从一开始就设计为与标志间隔“接近”。 ptest
只在SSE4.1中添加,经过多代设计,载体和EFLAGS
之间没有相互作用。
kmov
确实为你设置了popcnt,bsf或bsr。 ( bsf
/ jcc
没有宏定义,所以在搜索循环中,你可能仍然想测试/ jcc,只有bsf发现非零时,编码tzcnt的额外字节不会购买你任何东西,除非你正在做无bsf
东西,因为即使dest寄存器是未定义的, bsf
仍然将ZF设置为零输入,但lzcnt
给出了32 - bsr
,所以即使知道输入为非零时也是如此。)
我们也可以使用vcmpEQps
并以不同的方式结合我们的结果:
VFPCLASSPS k1, zmm0, 0x81 ; k1 = set where there are NaNs in A
VFPCLASSPS k2{k1}, zmm1, 0x81 ; k2 = set where there are NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
vcmpEQ_OQps k3, zmm0, zmm1
;; k3= 1 only where IEEE equal and ordered (cmpeqps normal operation)
; k3 k2
; 1 0 -> equal (ieee equal)
; 1 1 -> equal (ieee equal and both-NaN (impossible))
; 0 0 -> unequal (neither)
; 0 1 -> equal (both NaN)
KORTESTW k3, k2 ; CF = set iff k3|k2 is all-ones.
jc .reflexive_equal
这种方式只适用于与我们的向量中的元素数量完全匹配的kortest
大小。 例如,双精度元素的256b矢量只有4个元素,但kortestb
仍然根据输入掩码寄存器的低8位设置CF。
仅使用整数运算
除NaN外,+/- 0是IEEE_equal与bitwise_equal不同的唯一时间。 (除非我错过了一些东西,在使用之前仔细检查这个假设!) +0
和-0
所有位都为零,除了-0
的符号位已置位(MSB)。
如果我们忽略不同的NaN编码,那么bitwise_equal就是我们想要的结果,除了+/- 0的情况。 如果A和B是+/- 0,则A OR B
将全部为零,除非符号位。左移1会使其全部为零或不全为零,取决于我们是否需要覆盖按位 - 相等的测试。
这比cmpneqps
使用更多的指令,因为我们使用por
/ paddD
模拟我们需要的功能。 (或者pslld
加1,但是这比一个字节更长,它运行在不同于pcmpeq
端口上,但是您需要考虑周围代码的端口分布以将其纳入决策。)
该算法可能对不同的SIMD架构非常有用,该架构不提供用于检测NaN的相同矢量FP测试。
;inputs in xmm0:A xmm1:B
movaps xmm2, xmm0
pcmpeqd xmm2, xmm1 ; xmm2=bitwise_equal. (0:unequal -1:equal)
por xmm0, xmm1
paddD xmm0, xmm0 ; left-shift by 1 (one byte shorter than pslld xmm0, 1, and can run on more ports).
; xmm0=all-zero only in the +/- 0 case (where A and B are IEEE equal)
; xmm2 xmm0 desired result (0 means "no difference found")
; -1 0 -> 0 ; bitwise equal and +/-0 equal
; -1 non-zero -> 0 ; just bitwise equal
; 0 0 -> 0 ; just +/-0 equal
; 0 non-zero -> non-zero ; neither
ptest xmm2, xmm0 ; CF = ( (not(xmm2) AND xmm0) == 0)
jc reflexive_equal
等待时间比上面的cmpneqps
版本低一个或两个周期。
我们真的在这里充分利用了PTEST
:在两个不同的操作数之间使用ANDN,并使用它与整个事物的比较零。 我们不能用pandn / movmskps
替换它,因为我们需要检查所有位,而不仅仅是每个元素的符号位。
我没有真正测试过,所以即使我的结论+/- 0是唯一一次IEEE_equal不同于bitwise_equal(NaN除外)的结论也可能是错误的。
使用整数运算处理非按位相同的NaN可能不值得。 编码与+/- Inf非常类似,我不能想到任何简单的检查,不会需要几个指示。 Inf有所有的指数位设置,以及一个全零的尾数。 NaN具有设置的所有指数位,具有非零尾数aka有效数(因此有23位有效载荷)。 尾数的MSB被解释为is_quiet
标志以区分信令/安静的NaN。 另请参阅英特尔手册vol1,表4-3( Floating-Point Number and NaN Encodings
)。
如果它不是用-Inf使用前9位的编码,我们可以用A > 0x7f800000
的无符号比较来检查NaN。 ( 0x7f800000
是单精度+ Inf)。 但是请注意, pcmpgtd
/ pcmpgtq
是有符号整数比较。 AVX512F VPCMPUD
是一个无符号比较(dest =掩码寄存器)。
OP的想法: !(a<b) && !(b<a)
OP对!(a<b) && !(b<a)
无法正常工作,也没有任何变化。 你不能分辨出一个NaN和两个NaN之间的区别,只是从两个相反的操作数进行比较。 即使混合谓词也VCMPPS
: 没有VCMPPS
谓词将两个操作数中的一个操作数区VCMPPS
NaN ,或者取决于它是第一个还是第二个操作数是NaN。 因此,他们的组合不可能拥有这些信息。
Paul R的将矢量与自身进行比较的解决方案可以让我们检测出NaN的位置并“手动”处理它们。 两个操作数之间没有VCMPPS
结果的组合是足够的,但使用除A
和B
以外A
操作数确实有帮助。 (一种已知的非NaN载体或两次相同的操作数)。
没有反转,当至少有一个元素相等时,按位NaN代码会发现。 (对于pcmpeqd
没有任何pcmpeqd
,所以我们不能使用不同的逻辑运算符,仍然可以得到全等的测试):
; inputs in xmm0, xmm1
movaps xmm2, xmm0
cmpeqps xmm2, xmm1 ; -1:ieee_equal. EQ_OQ predicate in the expanded notation for VEX encoding
pcmpeqd xmm0, xmm1 ; -1:bitwise equal
orps xmm0, xmm2
; xmm0 = -1:(where an element is bitwise or ieee equal) 0:elsewhere
movmskps eax, xmm0
test eax, eax
jnz at_least_one_equal
; else all different
这种方式不适用于PTEST
,因为与OR结合是唯一有用的东西。
// UNFINISHED start of an idea
bitdiff = _mm_xor_si128(A, B);
signbitdiff = _mm_srai_epi32(bitdiff, 31); // broadcast the diff in sign bit to the whole vector
signbitdiff = _mm_srli_epi32(bitdiff, 1); // zero the sign bit
something = _mm_and_si128(bitdiff, signbitdiff);
这是一种可能的解决方案 - 但效率不高,需要6条指令:
__m128 v0, v1; // float vectors
__m128 v0nan = _mm_cmpeq_ps(v0, v0); // test v0 for NaNs
__m128 v1nan = _mm_cmpeq_ps(v1, v1); // test v1 for NaNs
__m128 vnan = _mm_or_si128(v0nan, v1nan); // combine
__m128 vcmp = _mm_cmpneq_ps(v0, v1); // compare floats
vcmp = _mm_and_si128(vcmp, vnan); // combine NaN test
bool cmp = _mm_testz_si128(vcmp, vcmp); // return true if all equal
请注意,上面的所有逻辑都是反转的,这可能会使代码有点难以遵循( OR
s实际上是AND
s,反之亦然)。
上一篇: SIMD instructions for floating point equality comparison (with NaN == NaN)
下一篇: What is the difference between (NaN != NaN) and (NaN !== NaN)?