Об особенностях оптимизации кода в GCC
1 сентября 2011
Сегодня товарищ redp озадачил меня интересным вопросом. Дескать, если современные компиляторы такие умные, то почему GCC не в состоянии преобразовать даже элементарный макрос инверсии байт двойного слова в ассемблерную инструкцию bswap?
Речь идет о коде вроде этого:
#include <time.h>
typedef unsigned int u32;
#define U8TO32_BE(p) \
(((u32)((p)[0]) << 24) | \
((u32)((p)[1]) << 16) | \
((u32)((p)[2]) << 8) | \
((u32)((p)[3]) ))
int main() {
u32 x = (u32)time(0);
printf("U8TO32_BE(%08x) = %08x\n", x,
U8TO32_BE((unsigned char*)&x));
return 0;
}
Действительно, как Visual Studio 2008, так и GCC 4.6 не в состоянии распознать в макросе U8TO32_BE простую команду bswap. Конечно, можно воспользоваться ассемблерными вставками или нестандартными расширениями языка типа _byteswap_ulong (не знаю, так ли оно называется в GCC), но эти методы плохи тем, что делают код зависимым от конкретного компилятора или архитектуры процессора.
Я переписал программу следующим образом:
#include <time.h>
typedef unsigned int u32;
#define BSWAP32(x) ( \
(((x) & 0xFF) << 24) | \
(((x) & 0xFF00) << 8) | \
(((x) & 0xFF0000) >> 8) | \
(((x) & 0xFF000000) >> 24))
int main() {
u32 x = (u32)time(0);
printf("BSWAP32(%08x) = %08x\n", x, BSWAP32(x));
return 0;
}
И посмотрел ассемблерный код, генерируемый GCC:
Необходимо указать тип процессора, потому что в i386 команды bswap не было. По умолчанию GCC ничего и никак не оптимизирует, потому флаг оптимизации также необходим. В результате получаем файл bswap.s следующего содержания:
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "BSWAP32(%08x) = %08x\n"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
movl $0, (%esp)
call time
movl $.LC0, (%esp)
movl %eax, %edx
bswap %edx
movl %eax, 4(%esp)
movl %edx, 8(%esp)
call printf
xorl %eax, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: 4.6.2 20110729 (prerelease)"
Как видите, bswap появился. Что интересно, GCC 4.2 (который вышел в 2008-м году) так не умеет.
Мораль в том, что современные компиляторы хоть и умны, но не настолько, чтобы распознать в серии получения указателей на переменные и обращения к элементам массива простую перестановку байт (еще раз смотрим, что и как делает U8TO32_BE). Чем проще код вы пишите, тем легче компилятору будет его оптимизировать. Например, когда вы пишете цикл, обходящий массив, не нужно извращаться с указателями. Используйте обычные индексы.
Метки: C/C++, Оптимизация, Отладка.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.