改变结束性,联合比位移更有效吗?

时间:2014-10-09 16:18:54

标签: c bit-shift endianness unions

我被要求接受改变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的底线。

2 个答案:

答案 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

,则会更好