用 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-a53
、a57
和 a75
的优化,要么 ext
实际上是一个不错的选择,也许比必须写入 2 个输出寄存器而不是逻辑上写入一个全角寄存器的 swp
更好。但通常这对 ARM 来说不是问题;相当多的指令可以做到这一点,而且它们通常会使它变得高效。
ARM's timing info for Cortex-A8 对 vext
和 vswp
有相同的数字(两者都是从 Qn
到 Q
输出的 1 个周期延迟,但是从Qm
到 Q
输出)。我没有检查更新的内核(或任何 64 位内核)。
我正在尝试使用 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
.
坚持前者,这是我的建议。
另一种表达这种随机播放的方法是使用 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-a53
、a57
和 a75
的优化,要么 ext
实际上是一个不错的选择,也许比必须写入 2 个输出寄存器而不是逻辑上写入一个全角寄存器的 swp
更好。但通常这对 ARM 来说不是问题;相当多的指令可以做到这一点,而且它们通常会使它变得高效。
ARM's timing info for Cortex-A8 对 vext
和 vswp
有相同的数字(两者都是从 Qn
到 Q
输出的 1 个周期延迟,但是从Qm
到 Q
输出)。我没有检查更新的内核(或任何 64 位内核)。