最小/最大寄存器内 SIMD 版本

swi*_*one 12 c assembly bit-manipulation arm64 swar

假设我有两个uint16_t[4]数组,a并且b. 这些数组中的每个整数都在 [0, 16383] 范围内,因此未设置第 14 和 15 位。a[i]然后我有一些代码来查找每个b[i]之间的最小值和最大值i

uint16_t min[4], max[4];
for (int i = 0; i < 4; i++) {
    if (a[i] < b[i]) {
        min[i] = a[i];
        max[i] = b[i];
    } else {
        min[i] = b[i];
        max[i] = a[i];
    }
}
Run Code Online (Sandbox Code Playgroud)

假设由于某种原因我不能/不会使用 SIMD,但我仍然想在 64 位平台上尽可能快地计算它。因此,一个自然的解决方案是在 64 位寄存器上使用 SIMD 寄存器内 (SWAR) 范例,在单次迭代中计算这 4 个值,而不是使用 16 位算术进行超过 4 次迭代。

可以使用哪些位操作技巧来使用 SWAR 范例来实现(最小或最大)或理想情况下这两种操作,以便生成的代码比上面的循环更快?我的目标架构是 ARMv8,因此请随意使用任何有助于减少指令数量的 ARMv8 指令。

C、汇编或 C+ 内联汇编解决方案都受欢迎。

fuz*_*fuz 7

您可以使用这样的代码,尽管它实际上比仅使用 SIMD 长很多:

orr     x2, x0, #0x8000800080008000     // x2 = 0x8000 | x0
sub     x2, x2, x1                      // x2 = (0x8000 | x0) - x1
and     x2, x2, #0x8000800080008000      // x2 = x0 < x1 ? 0x0000 : 0x8000
mov     x3, #0x7fff7fff7fff7fff
add     x2, x3, x2, lsr #15             // x2 = x0 < x1 ? 0x7fff : 0x8000
eor     x4, x0, x1                      // x4 = x0 ^ x1
and     x3, x4, x2                      // x3 = x0 < x1 ? x0 ^ x1 : 0x0000
eor     x4, x1, x3                      // x4 = x0 < x1 ? x0 : x1
eor     x3, x0, x3                      // x3 = x0 < x1 ? x1 : x0
Run Code Online (Sandbox Code Playgroud)

该算法的关键路径有6条指令。说明

mov     x3, #0x7fff7fff7fff7fff
eor     x4, x0, x1                      // x4 = x0 ^ x1
Run Code Online (Sandbox Code Playgroud)

不在关键路径上。如果在循环中执行,则恒定负载可能会被提升。最后两条指令可以独立评估,以相同的延迟产生最小值和最大值。