Самый быстрый способ изменить порядок байтов - PullRequest
3 голосов
/ 02 сентября 2011

Какой самый быстрый способ инвертировать порядковый номер 16-битного и 32-битного целого числа. Я обычно делаю что-то вроде (это кодирование было сделано в Visual Studio в C ++):

union bytes4
{
    __int32 value;
    char ch[4];
};

union bytes2
{
    __int16 value;
    char ch[2];
};

__int16 changeEndianness16(__int16 val)
{
    bytes2 temp;
    temp.value=val;

    char x= temp.ch[0];
    temp.ch[0]=temp.ch[1];
    temp.ch[1]=x;
    return temp.value;
}

__int32 changeEndianness32(__int32 val)
{
    bytes4 temp;
    temp.value=val;
    char x;

    x= temp.ch[0];
    temp.ch[0]=temp.ch[1];
    temp.ch[1]=x;

    x= temp.ch[2];
    temp.ch[2]=temp.ch[3];
    temp.ch[3]=x;
    return temp.value;
}

Есть ли более быстрый способ сделать то же самое, в котором мне не нужно делать так много вычислений?

Ответы [ 4 ]

8 голосов
/ 02 сентября 2011

Почему вы не используете встроенную функцию swab, которая, вероятно, оптимизирована лучше, чем ваш код?

Кроме того, обычные операции сдвига битов должны начинаться быстро и настолько широко использоваться, что могут распознаваться оптимизатором и заменяться еще более качественным кодом.


Поскольку у других ответов есть серьезные ошибки, я опубликую более лучшую реализацию:

int16_t changeEndianness16(int16_t val)
{
    return (val << 8) |          // left-shift always fills with zeros
          ((val >> 8) & 0x00ff); // right-shift sign-extends, so force to zero
}

Ни один из протестированных мной компиляторов не сгенерировал rolw для этого кода, я думаю, что более длинная последовательность (с точки зрения количества команд) на самом деле быстрее. Тесты были бы интересны.

Для 32-битных операций возможны несколько порядков:

//version 1
int32_t changeEndianness32(int32_t val)
{
    return (val << 24) |
          ((val <<  8) & 0x00ff0000) |
          ((val >>  8) & 0x0000ff00) |
          ((val >> 24) & 0x000000ff);
}

//version 2, one less OR, but has data dependencies
int32_t changeEndianness32(int32_t val)
{
    int32_t tmp = (val << 16) |
                 ((val >> 16) & 0x00ffff);
    return ((tmp >> 8) & 0x00ff00ff) | ((tmp & 0x00ff00ff) << 8);
}
5 голосов
/ 02 сентября 2011

По крайней мере в Visual C ++ вы можете использовать _byteswap_ulong () и друзей: http://msdn.microsoft.com/en-us/library/a3140177.aspx

Эти функции обрабатываются компилятором VC ++ как встроенные, и в результате создается сгенерированный код, использующий преимущества аппаратной поддержки.по мере доступности.С VC ++ 10.0 SP1 я вижу следующий сгенерированный код для x86:

return _byteswap_ulong(val);

mov     eax, DWORD PTR _val$[esp-4]
bswap   eax
ret     0

return _byteswap_ushort(val);

mov     ax, WORD PTR _val$[esp-4]
mov     ch, al
mov     cl, ah
mov     ax, cx
ret     0
2 голосов
/ 02 сентября 2011

Кто сказал, что слишком много вычислений?

out = changeEndianness16(in);

gcc 4.6.0

movzwl  -4(%rsp), %edx
movl    %edx, %eax
movsbl  %dh, %ecx
movb    %cl, %al
movb    %dl, %ah
movw    %ax, -2(%rsp)

clang++ 2.9

movw    -2(%rsp), %ax
rolw    $8, %ax
movw    %ax, -4(%rsp)

Intel C/C++ 11.1

movzwl    4(%rsp), %ecx
rolw      $8, %cx
xorl      %eax, %eax
movw      %cx, 6(%rsp)

Что производит ваш компилятор?

1 голос
/ 02 сентября 2011

Я использовал следующий код для функции обмена 16-битной версией:

_int16 changeEndianness16(__int16 val)
{
    return ((val & 0x00ff) << 8) | ((val & 0xff00) >> 8);
}    

С g ++ (Ubuntu / Linaro 4.4.4-14ubuntu5) 4.4.5 вышеуказанный код при компиляциис g++ -O3 -S -fomit-frame-pointer test.cpp приводит к следующему (не встроенному) ассемблерному коду:

movzwl  4(%esp), %eax
rolw    $8, %ax
ret

Следующий код эквивалентен, но g ++ не так хорош в его оптимизации.

__int16 changeEndianness16_2(__int16 val)
{
    return ((val & 0xff) << 8) | (val >> 8);
}

Компиляцияэто дает больше кода ASM:

movzwl  4(%esp), %edx
movl    %edx, %eax
sarl    $8, %eax
sall    $8, %edx
orl     %edx, %eax
ret
...