【问题标题】:128-bit rotation using ARM Neon intrinsics使用 ARM Neon 内在函数的 128 位旋转
【发布时间】:2012-06-30 20:32:27
【问题描述】:

我正在尝试使用 Neon 内在函数优化我的代码。我在 128 位数组上进行了 24 位旋转(每个 uint16_t 8 个)。

这是我的 c 代码:

uint16_t rotated[8];
uint16_t temp[8];
uint16_t j;
for(j = 0; j < 8; j++)
{
     //Rotation <<< 24  over 128 bits (x << shift) | (x >> (16 - shift)
     rotated[j] = ((temp[(j+1) % 8] << 8) & 0xffff) | ((temp[(j+2) % 8] >> 8) & 0x00ff);
}

我查看了关于 Neon Intrinsics 的 gcc 文档,它没有关于矢量旋转的说明。此外,我尝试使用vshlq_n_u16(temp, 8) 来执行此操作,但所有移出uint16_t 字的位都丢失了。

如何使用霓虹内在函数来实现这一点?顺便问一下,有没有更好的关于 GCC Neon Intrinsics 的文档?

【问题讨论】:

  • armcc 具有 __ror 内在
  • 如何使用带有ROR ARM 指令的内联汇编?
  • 我更喜欢避免组装。顺便说一句,我使用的是 GCC,所以没有 armcc!
  • GCC 也支持 ARM 汇编。

标签: c rotation intrinsics neon


【解决方案1】:

我不是 100% 确定,但我认为 NEON 没有旋转指令。

您可以使用左移、右移和或来组合所需的旋转操作,例如:

uint8_t ror(uint8_t in, int rotation)
{
    return (in >> rotation) | (in << (8-rotation));
}

只需对 Neon 内在函数执行相同的操作即可实现左移、右移和或。

uint16x8_t temp;
uint8_t rot;

uint16x8_t rotated =  vorrq_u16 ( vshlq_n_u16(temp, rot) , vshrq_n_u16(temp, 16 - rot) );

参见http://en.wikipedia.org/wiki/Circular_shift“实现循环移位”。

这将旋转车道内的值。如果您想自己旋转车道,请按照其他答案中的说明使用 VEXT。

【讨论】:

  • 我不是在问如何在 c 中进行圆周旋转!我在问如何使用 Neon Intrinsics 来做到这一点!
  • 好的,我已经添加了实际的内部调用。
  • 这比 OP 的答案(3 条指令而不是 5 条指令)要好,但除非 vext.8 与字节移位指令相比真的很慢,否则它仍然效率低下。
【解决方案2】:

在阅读了Arm Community Blogs 之后,我发现了这个:

VEXT:提取 VEXT 从一对现有向量中提取一个新的字节向量。新向量中的字节来自第一个操作数的顶部和第二个操作数的底部。这允许您生成一个新向量,其中包含跨越一对现有向量的元素。 VEXT 可用于实现来自两个向量的数据的移动窗口,这在 FIR 滤波器中很有用。 对于置换,当对两个输入操作数使用相同的向量时,它还可用于模拟按字节旋转操作。

以下 Neon GCC Intrinsic 与图片中提供的程序集相同:

uint16x8_t vextq_u16 (uint16x8_t, uint16x8_t, const int)

因此,可以通过以下方式对完整的 128 位向量(而不是每个元素)进行 24 位旋转:

uint16x8_t input;
uint16x8_t t0;
uint16x8_t t1;
uint16x8_t rotated;

t0 = vextq_u16(input, input, 1);
t0 = vshlq_n_u16(t0, 8);
t1 = vextq_u16(input, input, 2);
t1 = vshrq_n_u16(t1, 8);
rotated = vorrq_u16(t0, t1);

【讨论】:

  • 除非我遗漏了什么,否则与 vextq_u8 在一条指令中完成整个旋转相比,这过于复杂了。
【解决方案3】:

使用vext.8 将向量与其自身连接,并为您提供所需的 16 字节窗口(在本例中偏移 3 个字节)。

使用内部函数 requires casting 来保持编译器满意,但它仍然是一条指令:

#include <arm_neon.h>

uint16x8_t byterotate3(uint16x8_t input) {
    uint8x16_t tmp = vreinterpretq_u8_u16(input);
    uint8x16_t rotated = vextq_u8(tmp, tmp, 16-3);
    return vreinterpretq_u16_u8(rotated);
}

g++5.4 -O3 -march=armv7-a -mfloat-abi=hard -mfpu=neon (on Godbolt) 编译成这样:

byterotate3(__simd128_uint16_t):
    vext.8  q0, q0, q0, #13
    bx      lr

计数为 16-3 意味着我们向左旋转 3 个字节。 (这意味着我们从左向量中取 13 个字节,从右向量中取 3 个字节,所以它也是右旋转 13)。


相关:x86 还具有将滑动窗口放入两个寄存器串联的指令:palignr(在 SSSE3 中添加)。


也许我遗漏了一些关于 NEON 的信息,但我不明白为什么 OP 的自我回答是使用具有 16 位粒度的 vext.16 (vextq_u16)。它甚至不是一条不同的指令,只是 vext.8 的别名,这使得无法使用奇数计数,需要额外的指令。 The manual for vext.8 says:

VEXT 伪指令

您可以将数据类型指定为 16、32 或 64 而不是 8。在此 在这种情况下,#imm 指的是半字、字或双字,而不是 指的是字节,允许的范围是相应的 减少。

【讨论】:

    猜你喜欢
    • 2012-02-22
    • 2020-05-28
    • 1970-01-01
    • 2013-09-18
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    • 2014-05-06
    相关资源
    最近更新 更多