ARM 中 16 位大端到小端的快速转换

Fast conversion of 16-bit big-endian to little-endian in ARM

我需要将 16 位整数值的大数组从大端格式转换为小端格式。

现在我使用以下函数进行转换:

inline void Reorder16bit(const uint8_t * src, uint8_t * dst)
{
    uint16_t value = *(uint16_t*)src;
    *(uint16_t*)dst = value >> 8 | value << 8;
}

void Reorder16bit(const uint8_t * src, size_t size, uint8_t * dst)
{
    assert(size%2 == 0);
    for(size_t i = 0; i < size; i += 2)
        Reorder16bit(src + i, dst + i);
}

我使用 GCC。目标平台是 ARMv7 (Raspberry Phi 2B)。

有什么办法可以优化吗?

加载音频样本需要这种转换,它可以是小端格式,也可以是大端格式。当然现在不是瓶颈,但是占总处理时间的10%左右。而且我认为对于这样一个简单的操作来说太多了。

您想测量一下哪个更快,但是 Reorder16bit 的替代主体是

*(uint16_t*)dst = 256 * src[0] + src[1];

假设您的本机整数是小端。另一种可能性:

dst[0] = src[1];
dst[1] = src[0];

如果您想提高代码的性能,您可以执行以下操作:

1) 一步处理 4 个字节:

inline void Reorder16bit(const uint8_t * src, uint8_t * dst)
{
    uint16_t value = *(uint16_t*)src;
    *(uint16_t*)dst = value >> 8 | value << 8;
}

inline void Reorder16bit2(const uint8_t * src, uint8_t * dst)
{
    uint32_t value = *(uint32_t*)src;
    *(size_t*)dst = (value & 0xFF00FF00) >> 8 | (value & 0x00FF00FF) << 8;
}

void Reorder16bit(const uint8_t * src, size_t size, uint8_t * dst)
{
    assert(size%2 == 0);

    size_t alignedSize = size/4*4;
    for(size_t i = 0; i < alignedSize; i += 4)
        Reorder16bit2(src + i, dst + i);
    for(size_t i = alignedSize; i < size; i += 2)
        Reorder16bit(src + i, dst + i);
}

如果你使用64位平台,同样的方法一步处理8个字节是可以的。

2) ARMv7 平台支持称为 NEON 的 SIMD 指令。 通过使用它们,您可以比 1):

中更快地编写代码
inline void Reorder16bit(const uint8_t * src, uint8_t * dst)
{
    uint16_t value = *(uint16_t*)src;
    *(uint16_t*)dst = value >> 8 | value << 8;
}

inline void Reorder16bit8(const uint8_t * src, uint8_t * dst)
{
    uint8x16_t _src = vld1q_u8(src);
    vst1q_u8(dst, vrev16q_u8(_src));
}

void Reorder16bit(const uint8_t * src, size_t size, uint8_t * dst)
{
    assert(size%2 == 0);

    size_t alignedSize = size/16*16;
    for(size_t i = 0; i < alignedSize; i += 16)
        Reorder16bit8(src + i, dst + i);
    for(size_t i = alignedSize; i < size; i += 2)
        Reorder16bit(src + i, dst + i);
}

如果它是专门针对 ARM 的,那么有一个 REV 指令,特别是 REV16,它一次可以执行两个 16 位整数。

我对ARM指令集不是很了解,但我猜应该有一些特殊的指令可以进行字节序转换。显然,ARMv7 有 rev 等东西

您是否尝试过内置编译器 __builtin_bswap16?它应该编译成 CPU-specific 代码,例如在 ARM 上转。此外,它有助于编译器识别您实际上是在进行字节交换,并根据该知识执行其他优化,例如在 y=swap(x); y &= some_value; x = swap(y);.

这样的情况下完全消除冗余字节交换

我用谷歌搜索了一下,this thread discusses an issue with the optimization potential. According to this discussion,如果 CPU 支持 vrev NEON 指令,编译器也可以向量化转换。

https://goo.gl/4bRGNh

int swap(int b) {
  return __builtin_bswap16(b);
}

变成

swap(int):
        rev16   r0, r0
        uxth    r0, r0
        bx      lr

所以你的可以写成 (gcc-explorer: https://goo.gl/HFLdMb)

void fast_Reorder16bit(const uint16_t * src, size_t size, uint16_t * dst)
{
    assert(size%2 == 0);
    for(size_t i = 0; i < size; i++)
        dst[i] = __builtin_bswap16(src[i]);
} 

这应该构成 for 循环

.L13:
        ldrh    r4, [r0, r3]
        rev16   r4, r4
        strh    r4, [r2, r3]    @ movhi
        adds    r3, r3, #2
        cmp     r3, r1
        bne     .L13

GCC builtin docs 阅读更多关于 __builtin_bswap16 的信息。

Neon 建议(经过测试,gcc-explorer:https://goo.gl/fLNYuc):

void neon_Reorder16bit(const uint8_t * src, size_t size, uint8_t * dst)
{
  assert(size%16 == 0);
  //uint16x8_t vld1q_u16 (const uint16_t *) 
  //vrev64q_u16(uint16x8_t vec);
  //void vst1q_u16 (uint16_t *, uint16x8_t) 
  for (size_t i = 0; i < size; i += 16)
    vst1q_u8(dst + i, vrev16q_u8(vld1q_u8(src + i)));
}

变成

.L23:
        adds    r5, r0, r3
        adds    r4, r2, r3
        adds    r3, r3, #16
        vld1.8  {d16-d17}, [r5]
        cmp     r1, r3
        vrev16.8        q8, q8
        vst1.8  {d16-d17}, [r4]
        bhi     .L23

在此处查看有关霓虹灯内在函数的更多信息:https://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/ARM-NEON-Intrinsics.html

来自 ARM ARM A8.8.386 的奖励:

VREV16(Vector Reverse in halfwords)将向量的每个半字中的8位元素的顺序反转,并将结果放入相应的目标向量中。

VREV32(Vector Reverse in words)将vector的每个word中的8位或16位元素的顺序进行反转,并将结果放入对应的目的向量中.

VREV64 (Vector Reverse in doublewords) 将向量的每个双字中的8位、16位或32位元素的顺序反转,并将结果放入在相应的目标向量中。

除大小外,数据类型之间没有区别。