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 指令或算术/逻辑表达式来解决上述问题?
“在 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)