Меняется ли порядок байтов, является ли объединение более эффективным, чем битовые сдвиги?
Меня попросили принять вызов, чтобы изменить порядковый номер типа int. У меня была идея использовать битовые сдвиги
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
команда), в то время как способ сдвига битов занимает около 15 секунд на 2 000 000 000 итераций. Дело в том, что если я скомпилирую с -O1, оба метода займут всего 1 секунду, а 0,001 секунды с -O2 или -O3.
Методы битового сдвига компилируются в bswap
в ASM с -02 и -03, но не в способе объединения, gcc, похоже, распознает наивный шаблон, но не сложный способ объединения, чтобы это сделать. В заключение прочитайте нижнюю строку @user3386109.
2 ответа
Вот правильный код для функции замены байтов
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
функция, и помещает это непосредственно в код сборки, полностью обходя функцию.
Суть. Напишите свой код так, как это имеет смысл, и позвольте компилятору выполнить оптимизацию. Компиляторы в наши дни потрясающие. Использование хитрых оптимизаций в исходном коде (например, в союзах) может победить оптимизации, встроенные в компилятор, что на самом деле приведет к более медленному коду.
Вы также можете использовать этот код, который может быть немного более эффективным:
#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);
}
Это скомпилировано gcc на amd64 в следующую сборку:
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
,