XNOR 8 位块中的两个 64 位寄存器

0as*_*asm 5 assembly bit-manipulation x86-64 simd

我有两个 64 位值,我想按如下方式对它们进行 XNOR:

RAX: 01000001 | 01000010 | 01000011 | 01000001 | 01000101 | 01000110 | 01000111 | 01000001     XNOR
RBX: 01000001 | 01000001 | 01000001 | 01000001 | 01000001 | 01000001 | 01000001 | 01000001
-------------------------------------------------------------------------------------------
RCX:    1          0          0          1          0          0          0          1



XNOR does the following:
    1 XNOR 1 | 1
    1 XNOR 0 | 0
    0 XNOR 1 | 0 
    0 XNOR 0 | 1
Run Code Online (Sandbox Code Playgroud)

以便每次 XNOR 结果都准确地0xff输出1到 RCX 寄存器中相应的块位置。

是否有 I64 指令或算术/逻辑表达式来解决上述问题?

Pet*_*des 3

“在 8 位块中”部分使其与按位 XNOR 非常不同。您希望使用 AND 以 8 位块的形式水平减少 XNOR 结果。 这就是SIMD 的意义所在

您想要的具体操作是比较相等性。幸运的是,x86 SSE2(或 MMX)pcmpeqb xmm0, xmm1正是这样做的,在比较相等的元素中生成 0xFF (-1),在其他元素中生成 0x00。您可以movq xmm0, src对其进行设置,将 8 字节零扩展加载到 16 字节 XMM 寄存器中。

您可以使用 得到结果(从 XMM0 的低 8 个字节)到 RCX 中movq rcx, xmm0,其中 absf rcx, rcx将找到最低非零位的位置。或者test rcx, rcx如果有任何非零位,会让您分支。

如果你想要RCX = 0x0100000100000001(即每个字节底部有1位),你可以在MOVQ之前使用SSSE3pabsb xmm0, xmm0来进行字节的打包绝对值,映射0xFF -> 1并保持0不变。与 SSE2 不同,这不是x86-64的基准,但缺乏它的 CPU 已经完全过时了(比如最新的 AMD Phenom II)。


将 SIMD 比较结果转换为整数寄存器的正常方法是pmovmskb 它的效率与movq r, x但允许您获取所有 16 字节元素,甚至无需使用 64 位寄存器。

    movq     xmm0, [rdi]       ; 8-byte load.  Use movdqu for all 16 bytes
    movq     xmm1, [rsi]
    pcmpeqb  xmm0, xmm1
    pmovmskb ecx, xmm0

    cmp      ecx, 0xffff
    je       all_were_equal

    test     cl, cl        ; low 8 bytes of compare result -> low 8 bits of RCX
    jnz      some_were_equal
Run Code Online (Sandbox Code Playgroud)

这取每个字节的高位。即给你一个比较位图。您可以bsf ecx, ecx查找 16 个字节中的哪个(如果有)是第一个匹配项。(如果您的输入是零扩展的 8 字节值,则第 9 个字节将始终匹配。CH 将为 pmovmskb 输入上半部分的全 1。)

当然,您可以简单地对其进行分支,而不是对比较结果进行位扫描。常见的方式有:

  • test ecx, ecx/jnz如果有任何元素比较 true 则跳转
  • cmp ecx, 0xffff/je如果全部匹配则跳转。

相关:将 16 字节字符串与 SSE 进行比较,以使用内在函数执行此操作。


可以使用 MMX movq mm0, [rdi]/来做到这一点,但在一些最新的 CPU 上,MMX 的吞吐量比 SSE2 差(例如 Skylake 上的执行端口较少),并且当您完成恢复 x87 状态时pcmpeqb mm0, [rsi],您需要慢一些emms到 x87 模式。

不过,如果您的数据自然是 8 字节块,那么您将保存 a ,movq因此您自然不能一次只处理 16 字节。而且指令更加紧凑(机器代码大小),正如您可以在英特尔手册中看到它们的编码。因此,如果 8 字节块确实非常适合,并且您可以将 EMMS 排除在足够大的循环之外,那么 MMX 值得考虑。(或者如果你绝对从不使用x87指令,甚至不调用任何库函数,并且可以跳过EMMS)