在ARM

时间:2015-11-26 06:36:28

标签: c++ arm simd neon

我需要将16位整数值的大数组从big-endian转换为little-endian格式。

现在我用于转换以下功能:

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)。

有没有办法优化它?

这种转换是加载音频样本所必需的,可以像big-endian格式一样使用little-endian。当然,它现在不是瓶颈,但它占总处理时间的10%左右。我认为这对于这么简单的操作来说太过分了。

5 个答案:

答案 0 :(得分:8)

如果您想提高代码的效果,可以进行以下操作:

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);
}

答案 1 :(得分:8)

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]);
} 

应该为循环

.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的更多信息。

霓虹灯建议(有点测试,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 (向量反向半字)反转向量的每个半字中8位元素的顺序,并将结果放在相应的目标向量中。

VREV32 (向量反向字)反转向量的每个字中的8位或16位元素的顺序,并将结果放在相应的目标向量中。

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

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

vrev diagram

答案 2 :(得分:6)

如果它专门用于ARM,则有一条REV指令,特别是REV16,它一次可以执行两个16位整数。

答案 3 :(得分:6)

我对ARM指令集知之甚少,但我想有一些关于endianess转换的特殊指令。显然,ARMv7有类似rev等的东西。

您是否尝试过编译器内在__builtin_bswap16?它应编译为特定于CPU的代码,例如ARM上的rev。此外,它有助于编译器识别您实际上在进行字节交换,并使用该知识执行其他优化,例如,在像y=swap(x); y &= some_value; x = swap(y);这样的情况下完全消除冗余字节交换。

我用Google搜索了一下,this thread discusses an issue with the optimization potentialAccording to this discussion,如果CPU支持vrev NEON指令,编译器也可以对转换进行矢量化。

答案 4 :(得分:2)

您想要衡量哪个更快,但Reorder16bit的替代机构

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

假设您的本地int是little-endian。另一种可能性:

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