我被要求接受改变int的endianess的挑战。我的想法是使用bitshifts
int swap_endianess(int color)
{
int a;
int r;
int g;
int b;
a = (color & (255 << 24)) >> 24;
r = (color & (255 << 16)) >> 16;
g = (color & (255 << 8)) >> 8;
b = (color & 255)
return (b << 24 | g << 16 | r << 8 | a);
}
但有人告诉我,使用包含int和四个字符数组的联合更容易(如果int存储在4个字符中),填充int然后反转数组。
union u_color
{
int color;
char c[4];
};
int swap_endianess(int color)
{
union u_color ucol;
char tmp;
ucol.color = color;
tmp = ucol.c[0];
ucol.c[0] = ucol.c[3];
ucol.c[3] = tmp;
tmp = ucol.c[1];
ucol.c[1] = ucol.c[2];
ucol.c[2] = tmp;
return (ucol.color);
}
在这两者之间交换字节的更有效方法是什么?有更有效的方法吗?
在对I7进行测试后,联合方式大约需要24秒(使用time
命令测量),而在2,000,000,000次迭代中,位移方式大约需要15秒。
如果我用-O1编译,两种方法只需1秒,而-O2或-O3只需0.001秒。
bitshift方法在ASM中使用-02和-03编译为bswap
,但不是联合方式,gcc似乎认识到了天真的模式而不是复杂的联合方式。最后,请阅读@ user3386109的底线。
答案 0 :(得分:3)
这是字节交换功能的正确代码
uint32_t changeEndianess( uint32_t value )
{
uint32_t r, g, b, a;
r = (value >> 24) & 0xff;
g = (value >> 16) & 0xff;
b = (value >> 8) & 0xff;
a = value & 0xff;
return (a << 24) | (b << 16) | (g << 8) | r;
}
这是一个测试字节交换功能的函数
void testEndianess( void )
{
uint32_t value = arc4random();
uint32_t result = changeEndianess( value );
printf( "%08x %08x\n", value, result );
}
使用LLVM编译器进行完全优化,testEndianess
函数的结果汇编代码为
0x93d0: calll 0xc82e ; call `arc4random`
0x93d5: movl %eax, %ecx ; copy `value` into register CX
0x93d7: bswapl %ecx ; <--- this is the `changeEndianess` function
0x93d9: movl %ecx, 0x8(%esp) ; put 'result' on the stack
0x93dd: movl %eax, 0x4(%esp) ; put 'value' on the stack
0x93e1: leal 0x6536(%esi), %eax ; compute address of the format string
0x93e7: movl %eax, (%esp) ; put the format string on the stack
0x93ea: calll 0xc864 ; call 'printf'
换句话说,LLVM编译器识别整个changeEndianess
函数并将其实现为单个bswapl
指令。
对那些想知道为什么需要调用arc4random
的人注意到。鉴于此代码
void testEndianess( void )
{
uint32_t value = 0x11223344;
uint32_t result = changeEndianess( value );
printf( "%08x %08x\n", value, result );
}
编译器生成此程序集
0x93dc: leal 0x6524(%eax), %eax ; compute address of format string
0x93e2: movl %eax, (%esp) ; put the format string on the stack
0x93e5: movl $0x44332211, 0x8(%esp) ; put 'result' on the stack
0x93ed: movl $0x11223344, 0x4(%esp) ; put 'value' on the stack
0x93f5: calll 0xc868 ; call 'printf'
换句话说,给定硬编码的value
作为输入,编译器预先计算result
函数的changeEndianess
,并将其直接放入汇编代码中,完全绕过函数。
底线。以编写代码的方式编写代码,让编译器进行优化。这些天编译器是惊人的。在源代码中使用棘手的优化(例如,联合)可能会破坏编译器中内置的优化,实际上会导致代码变慢。
答案 1 :(得分:2)
您也可以使用此代码,效率稍高一些:
#include <stdint.h>
extern uint32_t
change_endianness(uint32_t x)
{
x = (x & 0x0000FFFFLU) << 16 | (x & 0xFFFF0000LU) >> 16;
x = (x & 0x00FF00FFLU) << 8 | (x & 0xFF00FF00LU) >> 8;
return (x);
}
这是由amd64上的gcc编译成以下程序集:
change_endianness:
roll $16, %edi
movl %edi, %eax
andl $16711935, %edi
andl $-16711936, %eax
salq $8, %rdi
sarq $8, %rax
orl %edi, %eax
ret
为了获得更好的结果,您可能希望使用嵌入式程序集。 i386和amd64架构提供bswap
指令来执行您想要的操作。正如user3386109所解释的那样,编译器可能会认识到“天真”的方法并发出bswap
指令,这是上述方法不会发生的。但是,如果编译器不够智能,无法检测到它可以使用bswap
。