我需要将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%左右。我认为这对于这么简单的操作来说太过分了。
答案 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)
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位元素的顺序,并将结果放在相应的目标向量中
数据类型之间没有区别,除了大小。
答案 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 potential。 According 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];