← На главную

Об особенностях оптимизации кода в GCC

Сегодня товарищ redp озадачил меня интересным вопросом. Дескать, если современные компиляторы такие умные, то почему GCC не в состоянии преобразовать даже элементарный макрос инверсии байт двойного слова в ассемблерную инструкцию bswap?

Речь идет о коде вроде этого:

#include <stdio.h> #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 <stdio.h> #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:

/usr/local/bin/gcc46 -O2 -march=i686 -S -c bswap.c

Необходимо указать тип процессора, потому что в i386 команды bswap не было. По умолчанию GCC ничего и никак не оптимизирует, потому флаг оптимизации также необходим. В результате получаем файл bswap.s следующего содержания:

.file "bswap.c" .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). Чем проще код вы пишите, тем легче компилятору будет его оптимизировать. Например, когда вы пишете цикл, обходящий массив, не нужно извращаться с указателями. Используйте обычные индексы.