用 C/gcc 内在函数交换 NEON 矢量的一半:VSWP 没有内在函数?

Swap halves of a NEON vector with C/gcc intrinsics: no intrinsic for VSWP?

我正在尝试使用 NEON 矢量指令做一些相对简单的事情: 给定 uint64x2_t,我想交换 64 位成员的位置。

又名,如果这是一个简单的普通代码:

typedef struct {
    U64 u[2];
} u64x2;


u64x2 swap(u64x2 in)
{
    u64x2 out;
    out.u[0] = in.u[1];
    out.u[1] = in.u[0];
    return out;
}

令人惊讶的是,我找不到它的内在函数。显然有一个汇编程序指令 (VSWP) 但没有相应的内在指令。

这很奇怪。这是一个尽可能微不足道的操作,所以它必须是可能的。问题是:如何?

edit :供参考,godbolt 使用@Jake 回答的结果: https://godbolt.org/z/ueJ6nB。 没有 vswp,但 vext 效果很好。

没错,NEON 内部函数不支持 VSWP 指令。

但是,您可以求助于 VEXT 指令,该指令也可用于内部函数。

out = vextq_u64(in, in, 1);


或者,您可以使用 vcombine(并祈祷编译器不会搞砸):

out = vcombine_U64(vget_high_u64(in), vget_low_u64(in));

但请注意,编译器在看到 vcombine and/or vget.

时往往会生成 FUBAR 机器码

坚持前者,这是我的建议。

另一种表达这种随机播放的方法是使用 GNU C native vector 内置函数,它提供与目标无关的方式来执行给定操作。根据目标支持的内容,编译时常量洗牌掩码可以优化为立即洗牌。但运行时变量随机播放可能效率低下,具体取决于目标 ISA 支持。

#include <arm_neon.h>

#ifndef __clang__
uint64x2_t swap_GNU_shuffle(uint64x2_t in)
{
    uint64x2_t mask = {1,0};
    uint64x2_t out = __builtin_shuffle (in, mask);
    return out;
}
#endif

AArch64 gcc8.2 on Godbolt 实际上编译为 Jake 建议的相同洗牌,而不是 SWP:

swap_GNU_shuffle:
        ext     v0.16b, v0.16b, v0.16b, #8
        ret

Clang 还优化了我们对 ext 指令的大部分纯 C 尝试,包括使用 memcpy 对普通结构进行类型双关并返回的指令。与 GCC 不同,它没有很好的洗牌优化器。 (在 Godbolt 上,使用下拉列表中的任何 clang-O3 -target arm64。clang 通常默认支持多个目标 ISA,这与 GCC 不同。)

所以要么所有这些编译器都错过了对 tune=generic 和 -mcpu=cortex-a53a57a75 的优化,要么 ext 实际上是一个不错的选择,也许比必须写入 2 个输出寄存器而不是逻辑上写入一个全角寄存器的 swp 更好。但通常这对 ARM 来说不是问题;相当多的指令可以做到这一点,而且它们通常会使它变得高效。

ARM's timing info for Cortex-A8vextvswp 有相同的数字(两者都是从 QnQ 输出的 1 个周期延迟,但是从QmQ 输出)。我没有检查更新的内核(或任何 64 位内核)。