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 指令,编译器也可以向量化转换。
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位元素的顺序反转,并将结果放入在相应的目标向量中。
除大小外,数据类型之间没有区别。
我需要将 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 指令,编译器也可以向量化转换。
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位元素的顺序反转,并将结果放入在相应的目标向量中。
除大小外,数据类型之间没有区别。