【问题标题】:XNOR two 64 bits registers in 8 bit blocksXNOR 两个 8 位块中的 64 位寄存器
【发布时间】:2021-06-02 10:20:32
【问题描述】:

我有两个 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

这样每次 XNOR 的结果正好是 0xff 它在 RCX 寄存器中的相应块位置输出 1

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

【问题讨论】:

  • 这可能更好地表示为矢量比较,pcmpeqb 非常接近您想要的。这将在比较相等的字节中留下0xff,而不是0x1,所以也许你在它后面跟着一个向量否定;我不确定是否只有一条指令,但是从 0 中减去向量就可以了。
  • 所以 XNOR 只是一个相等比较?
  • XNOR 在位级别上是相等的,是的(参见真值表)。但是您想要的并不是真正的按位 XNOR,因为您希望示例中的第二个字节为 0 而不是 11111100。所以我真的认为这是一个 bytewise 操作,而不是按位操作,从这个意义上说,将其称为比较比 XNOR 更有意义。这也向我表明 SIMD 是一条比 bit twiddling 更好的途径。
  • @NateEldredge: pabsb(字节绝对值)将映射 0xff -> 1 和 0x00 -> 0。但如果他们想要 RCX 中的结果,他们可能希望 pmovmskb ecx, xmm0 提取高每个向量字节的位,给出比较结果的位图。除非他们想要每个字节用零分隔 1 位。
  • 相关:Compare 16 byte strings with SSE 使用内在函数。使用 movq 加载(或从整数 reg 注册副本)而不是 movdqu 仅加载 8 个字节,如果这真的是你想要的。

标签: assembly bit-manipulation x86-64 simd


【解决方案1】:

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

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

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

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


将 SIMD 比较结果转换为整数 reg 的常规方法是 pmovmskb 它与 movq r, x 一样高效,但您甚至可以在不使用 64 位寄存器的情况下获取所有 16 字节元素。

    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

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

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

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

相关:Compare 16 byte strings with SSE 使用内部函数进行此操作。


可以使用 MMX movq mm0, [rdi] / pcmpeqb mm0, [rsi] 执行此操作,但在某些最新的 CPU 上,MMX 的吞吐量比 SSE2 更差(例如 Skylake 上的执行端口更少),而且您'当您完成将 x87 状态恢复为 x87 模式时,需要一个缓慢的 emms

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-24
    • 2014-11-18
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 2019-04-21
    • 2020-06-07
    • 2020-11-21
    相关资源
    最近更新 更多