用于浮点相等比较的SIMD指令(NaN == NaN)

哪些指令可用于比较由4 * 32位浮点值组成的两个128位向量?

是否有一条指令将双方的NaN值视为相等? 如果不是,提供反身性(即NaN等于NaN)的解决方法的性能影响有多大?

我听说,与IEEE语义相比,确保自反性会产生显着的性能影响,NaN并不等于自己,我想知道这种影响是否会大。

我知道您在处理浮点值时通常要使用epsilon比较而不是确切的质量。 但是这个问题是关于精确的相等比较,你可以用它来消除哈希集中的重复值。

要求

  • +0-0必须相等。
  • NaN必须与自身相等。
  • NaN的不同表示方式应该相同,但如果性能影响过大,则可能会牺牲该要求。
  • 结果应该是一个布尔值, true如果所有四个浮动元件在两种载体相同,并且如果至少一个元件不同假。 其中true由标量整数1false0
  • 测试用例

    (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 cmpeqpscmpeqps / 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编码:

  • 按位相等捕获两个相同的NaN。
  • IEEE等于获得了+0 == -0情况。
  • 有没有情况下,无论是比较给人一种假阳性(因为ieee_equal是假的,当一个操作数为NaN:我们只想要平等,不等于有或无序的AVX vcmpps 。提供了两个选项,而SSE只是提供了一个简单的等于操作)

    我们想知道什么时候所有的元素都是平等的,所以我们应该从倒数比较开始。 检查至少一个非零元素比检查所有非零元素更容易。 (即水平的AND很难,水平的OR很容易( pmovmskb / testptest )。相反的比较是免费的( 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
    

    对于双精度, ...PSpcmpeqQ工作原理是一样的。

    如果不相等的代码继续查找哪个元素不相等,则对movmskps结果进行位扫描会得出第一个差异的位置。

    使用SSE4.1 PTEST您可以使用andnps movmskps替换andnps / movmskps / test-and-branch:

    ptest    xmm0, xmm2   ; CF =  0 == (NOT(xmm0) AND xmm2).
    jc no_differences
    

    我预计这是大多数人第一次看到PTESTCF结果对任何事物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和任何关于ZFja测试CF=0 and ZF=1 。 你不可能只想测试它,所以把jnz放在jc分支目标中就可以正常工作。 (如果你只想测试equal_reflexiveat_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我们不需要PTESTCF结果。 我们在使用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的编码。 (试一下,也许我们可以避免使用PORPANDcmpps在每个操作数上合并来自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,我们可以使用vandpsvandnps (和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。 因此,我们保存orandn指令。 顺便提一句,我想知道为什么没有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结果的组合是足够的,但使用除AB以外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,反之亦然)。

    链接地址: http://www.djcxy.com/p/58501.html

    上一篇: SIMD instructions for floating point equality comparison (with NaN == NaN)

    下一篇: What is the difference between (NaN != NaN) and (NaN !== NaN)?