【问题标题】:Swap halves of a NEON vector with C/gcc intrinsics: no intrinsic for VSWP?用 C/gcc 内在函数交换 NEON 向量的一半:VSWP 没有内在函数?
【发布时间】:2019-12-04 01:15:33
【问题描述】:

我正在尝试使用 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),但没有相应的内在指令。

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

编辑:作为参考,godbolt 结果使用@Jake 答案: https://godbolt.org/z/ueJ6nB 。 没有vswp,但vext 效果很好。

【问题讨论】:

  • 你应该提到你的编译器。是海合会吗?还是 ARMCC?
  • 对,目前是gcc
  • 这里是适用于armcc 的引用: 这条指令作为内在函数没有任何好处,因为内在函数使用变量来封装寄存器分配和访问。因此。可以使用简单的 C 风格变量赋值来执行变量交换。。我想这同样适用于 GCC。
  • @Cyan:您只是使用 ABI 以整数 reg 而不是向量传递的普通结构。我添加了一个类型双关语和一个 GNU C 本机向量尝试 (godbolt.org/z/EPeMFo),它们仍然编译为 vec->int->vec。所以还是不行。

标签: c gcc arm intrinsics neon


【解决方案1】:

你说得对,NEON 内部函数不支持 VSWP 指令。

但是,您可以改用 VEXT 指令,该指令也可在内部函数中使用。

out = vextq_u64(in, in, 1);


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

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

但请注意,编译器在看到 vcombine 和/或 vget 时往往会生成 FUBAR 机器代码。

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

【讨论】:

  • @PeterCordes 是的,这正是我 VEXT 所做的——交换两半。
  • @Cyan 别担心。 VEXT 并不比 VSWP 慢,甚至更好,VEXT 被视为置换指令,因此在 Cortex-A8 上的不同管道中执行,这可能使其更快。
  • @PeterCordes 话虽如此,我们真正需要知道的是 ARM 的代表性有序内核的行为方式。代码在 Cortex-A53 上运行速度很快,在任何自定义乱序内核上都不会差。他们根本不应该。永远记住,它更多的是关于 ARM 的电源效率,而不是纯粹的性能。
  • @PeterCordes 另一个例子:Cortex-A15 具有“代码缓冲区”;如果循环由少于 16 条指令组成,包括末尾的分支指令,它们将进入代码缓冲区,在执行过程中省略获取和解码阶段,从而节省大量功率。如果它跑得更快,我也不会感到惊讶。从那以后事情变得更容易了。我不再激进地展开循环,并且倾向于更喜欢较短的代码。由于功率效率是首要考虑因素,因此除了 ARM 的周期数之外,还有许多其他因素。
  • 对于一些更现代的内核(例如 Cortex-A76 - developer.arm.com/docs/swog307215/latest/…),您肯定希望使用 VEXT(2 周期延迟)而不是 VSWP(4 周期延迟)。 Cortex-A75 更喜欢 VSWP 而不是 VEXT (static.docs.arm.com/101398/0200/…)
【解决方案2】:

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

#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 不同,GCC 没有那么好的 shuffle 优化器。 (在 Godbolt 上,使用 -O3 -target arm64 下拉列表中的任何 clang。与 GCC 不同,clang 通常默认支持多个目标 ISA。)

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

ARM's timing info for Cortex-A8vextvswp 具有相同的数字(两者都是从 QnQoutput 的 1 个周期延迟,但从 QmQoutput 是 2 个周期)。我没有检查过较新的内核(或任何 64 位内核)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    • 2013-09-16
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2019-09-03
    相关资源
    最近更新 更多