Исследование оптимизаций, выполняемых компилятором
Министерство образования и науки Российской Федерации
Санкт-Петербургский государственный политехнический университет
Факультет технической кибернетики
Кафедра «Информационная безопасность компьютерных систем»
ЛАБОРАТОРНАЯ РАБОТА № 1
Исследование оптимизаций, выполняемых компилятором
по дисциплине «Языки программирования»
Выполнил
студент гр. 2088/1
А.В. Лашевский
Старший
Преподаватель
П.В. Семьянов
Санкт-Петербург 2012
Содержание
1. Формулировка задания
. Постановка задачи
3. Результаты выполненной работы
3.1. Компилятор
.2 Ассемблерный листинг:
.4 Подбор опций компилятора
4. Выводы по проделанной работе
1. Формулировка задания
- Исследовать методы оптимизации кода на языке Си с помощью одного из компиляторов. Использовать для тестирования утилиту optbench.c
- Исследовать особенности оптимизации компилятора на собственной программе.
компилятор программный код утилита
2. Постановка задачи
- Выбрать один из предлагаемых компиляторов:
- Microsoft Visual Studio 2010
- GCC
- Intel C++ Compiler
- Исследовать различные варианты оптимизаций для выбранного компилятора с помощью optbench.c, составить таблицу о проведенных улучшениях.
- Привести примеры наиболее удачных и неудачных оптимизаций.
- Подобрать опции компилятора для получения:
- наиболее быстрого кода
- наиболее компактного кода
3. Результаты выполненной работы
3.1 Компилятор
Для изучения оптимизации кода, написанного на языке С (optbench.c), был выбран компилятор GCC-4.4.3, работающий под операционной системой Linux Ubuntu v10.04.
3.2 Ассемблерный листинг
Чтобы получить ассемблерный код программы optbench.c, который находится в домашней директории, используем команду: gcc -S optbench.c
В результате в домашней папке был создан файл optbench.s, содержащий ассемблерный листинг неоптимизированной программы.
Для получения ассемблерного кода с оптимизациями использовалась команда: gcc -S -O2 optbench.c
Таблица 1. Оптимизации компилятора
Исходный текст на СиНеоптимизированный код на ассемблереОптимизированный код на ассемблереРезультатыРазмножение констант и копийj4 = 2; if( i2 < j4 && i4 < j4 ) i2 = 2; j4 = k5; if( i2 < j4 && i4 < j4 ) i5 = 3; pushl %ebp movl %esp, %ebp andl $-16, %esp subl $48, %esp movl $2, j4 movl i2, %edx movl j4, %eax cmpl %eax, %edx jge .L2 movl i4, %edx movl j4, %eax cmpl %eax, %edx jge .L2 movl $2, i2 .L2: movl k5, %eax movl %eax, j4 movl i2, %edx movl j4, %eax cmpl %eax, %edx jge .L3 movl i4, %edx movl j4, %eax cmpl %eax, %edx jge .L3 movl $3, i5pushl %ebp movl %esp, %ebp andl $-16, %esp subl $28, %esp movl i2, %eax cmpl $1, %eax movl k5, %edx movl %edx, j4 Переменные заменяются константными значениями.Свертка константi3 = 1 + 2; flt_1 = 2.4 + 6.3; i2 = 5; L3: movl $3, i3 fldl .LC0 fstpl flt_1 movl $5, i2fldl .LC0 fstpl flt_1 movl $3, i3 movl $5, i2Производится свертка констант (независимо от выбора оптимизации).Алгебраические тождества и излишние операции загрузки/сохраненияj2 = i + 0; k2 = i / 1; i4 = i * 1; i5 = i * 0; <…> flt_3 = 2.4 / 1.0; flt_4 = 1.0 + 0.0000001; flt_5 = flt_6 * 0.0; flt_6 = flt_2 * flt_3; movl i, %eax movl %eax, j2 movl i, %eax movl %eax, k2 movl i, %eax movl %eax, i4 movl $0, i5 fldl .LC1 fstpl flt_3 fldl .LC2 fstpl flt_4 fldl flt_6 fldz fmulp %st, %st(1) fstpl flt_5 fldl flt_2 fldl flt_3 fmulp %st, %st(1) fstpl flt_6movl i, %eax fldl .LC1 fstl flt_3 fldl .LC2 fstpl flt_4 fldz fmull flt_6 movl %eax, j2 movl %eax, i4 fstpl flt_5 fmull flt_2 movl %eax, k2 fstpl flt_6 Исключено многократное помещение переменной i в регистр eax (исключены излишние операции загрузки). Вместо арифметических операций выполняется простое присваивание.Деление на нольi2 = i / 0; flt_2 = flt_1 / 0.0; --Компилятор gcc версии 4.4.3 выдает ошибку деления на ноль и не генерирует объектный код.Лишнее присваиваниеk3 = 1; k3 = 1; movl $1, k3 movl $1, k3movl $1, k3Исключается повторное присваивание значения переменной k3.Снижение мощностиk2 = 4 * j5; for( i = 0; i <= 5; i++ ) ivector4[ i ] = i * 2; movl j5, %eax sall $2, %eax movl %eax, k2 movl $0, i jmp .L4 .L5: movl i, %eax movl i, %edx addl %edx, %edx movw %dx, ivector4(%eax,%eax) movl i, %eax addl $1, %eax movl %eax, i .L4: movl i, %eax cmpl $5, %eax jle .L5movl j5, %eax movl $ivector4, %edx sall $2, %eax movl %eax, k2 xorl %eax, %eax L24: movw %ax, (%edx) addl $2, %eax addl $2, %edx cmpw $12, %ax jne .L24 Операция умножения заменяется более простым и быстрым сдвигом влево (sall). Шаг цикла увеличен в 2 раза, число переходов после оптимизации уменьшилось. Это должно уменьшить время работы.Простой циклj5 = 0; k5 = 10000; do { k5 = k5 - 1; j5 = j5 + 1; i5 = (k5 * 3) / (j5 * constant5); } while ( k5 > 0 ); movl $0, j5 movl $10000, k5 .L6: movl k5, %eax subl $1, %eax movl %eax, k5 movl j5, %eax addl $1, %eax movl %eax, j5 movl k5, %edx movl %edx, %eax addl %eax, %eax leal (%eax,%edx), %ecx movl j5, %edx movl %edx, %eax sall $2, %eax addl %edx, %eax movl %eax, 44(%esp) movl %ecx, %edx movl %edx, %eax sarl $31, %edx idivl 44(%esp) movl %eax, i5 movl k5, %eax testl %eax, %eax jg .L6movl $5, %ebx movl $29997, %ecx .L25: movl %ecx, %edx movl %ecx, %eax sarl $31, %edx subl $3, %ecx idivl %ebx addl $5, %ebx cmpl $-3, %ecx jne .L25 movl $10000, j5 xorl %edx, %edx movl %eax, i5 Значения переменных k5 и j5 были посчитаны заранее (вынесены из цикла).Управление переменной индукции циклаfor( i = 0; i < 100; i++ ) ivector5[ i * 2 + 3 ] = 5; ___________________________ for(i=0;i<100;i++) ivector5[i*3+3]=5; movl $0, i jmp .L7 .L8: movl i, %eax addl %eax, %eax addl $3, %eax movl $5, ivector5(,%eax,4) movl i, %eax addl $1, %eax movl %eax, i .L7: movl i, %eax cmpl $99, %eax jle .L8 movl $0, i jmp .L7 .L8: movl i, %eax leal 1(%eax), %edx movl %edx, %eax addl %eax, %eax addl %edx, %eax movl $5, ivector5(,%eax,4) movl i, %eax addl $1, %eax movl %eax, i .L7: movl i, %eax cmpl $99, %eax jle .L8.L26: movl $5, ivector5+12(%edx) addl $8, %edx cmpl $800, %edx jne .L26 movl $100, i .L43: movl $5, ivector5+12(%eax) addl $12, %eax cmpl $1200, %eax jne .L43 movl $100,i Использована замена переменной цикла. Тем самым в цикле не выполняются многократные сложные математические операции.Глубокие подвыраженияif( ni < 10 ) j5 = ni5 + ni2; else k5 = ni5 + ni2; movl ni, %eax cmpl $9, %eax jg .L9 movl ni5, %edx movl ni2, %eax leal (%edx,%eax), %eax movl %eax, j5 jmp .L10 .L9: movl ni5, %edx movl ni2, %eax leal (%edx,%eax), %eax movl %eax, k5 cmpl $9, ni jle .L38 movl ni2, %eax addl ni5, %eax movl %eax, k5 .L38: movl ni2, %eax addl ni5, %eax movl %eax, j5 Переменные i5 = 0, i2 = 5, i = 100, k5=5 уже имеют значения, поэтому часть кода после условного оператора if недостижима и игнорируется компилятором. Выполняться будет только код после оператора else. Поэтому необходимо ввести новые переменные ni, ni5, ni2, которыми заменим исходные. Значение суммы ni5+ni2 высчитывается два раза. Оптимизация глубоких подвыражений не производится.Проверка того, как компилятор генерирует адрес переменной с константным индексом, размножает копии и регистры ivector[ 0 ] = 1; /* генерация константного адреса */ ivector[ i2 ] = 2; /* значение i2 должно быть скопировано*/ ivector[ i2 ] = 2; /* копирование регистров */ ivector[ 2 ] = 3; /* генарация константного адреса */movl $1, ivector movl i2, %eax movl $2, ivector(,%eax,4) movl i2, %eax movl $2, ivector(,%eax,4) movl $3, ivector+8 movl $1, ivector movl $2, ivector+20 movl $3, ivector+8 Компилятор генерирует переменную с константным адресом. Повторного присваивания не произошло.Удаление общих подвыраженийif(( nh3 + nk3 ) < 0 || ( nh3 +nk3 ) > 5 ) printf("Common subexpression elimination\n"); else { m3 = ( nh3 + nk3 ) / i3; g3 = i3 + (nh3 + nk3); printf(%d, m3); printf(%d, g3); } movl nh3, %edx movl nk3, %eax leal (%edx,%eax), %eax testl %eax, %eax js .L11 movl nh3, %edx movl nk3, %eax leal (%edx,%eax), %eax cmpl $5, %eax jle .L12 .L11: movl $.LC4, (%esp) call puts jmp .L13 .L12: movl nh3, %edx movl nk3, %eax leal (%edx,%eax), %eax movl i3, %edx movl %edx, 44(%esp) movl %eax, %edx sarl $31, %edx idivl 44(%esp) movl %eax, m3 movl nh3, %edx movl nk3, %eax addl %eax, %edx movl i3, %eax leal (%edx,%eax), %eax movl %eax, g3 .L13: movl m3, %edx movl $.LC5, %eax movl %edx, 4(%esp) movl %eax, (%esp) call printf movl g3, %edx movl $.LC5, %eax movl %edx, 4(%esp) movl %eax, (%esp) call printf movl nk3, %ecx addl $5, %eax addl nh3, %ecx movl %eax, k5 cmpl $5, %ecx ja .L36 movl %ecx, %eax movl $1431655766, %edx imull %edx movl %ecx, %eax sarl $31, %eax addl $3, %ecx movl %ecx, g3 subl %eax, %edx movl %edx, m3 .L28: movl %edx, 8(%esp) movl $.LC5, 4(%esp) movl $1, (%esp) call __printf_chk movl g3, %eax movl $.LC5, 4(%esp) movl $1, (%esp) movl %eax, 8(%esp) call __printf_chk .L36: movl $.LC4, 4(%esp) movl $1, (%esp) call __printf_chk movl m3, %edx jmp .L28Компилятор выполняет суммирование h3+k3 всего один раз перед условным оператором и использует полученное значение в дальнейшем как уже известное. Т.е. происходит удаление общих подвыражений.Вынесение инвариантного кода (j * k) может быть вынесено из циклаfor( i4 = 0; i4 <= np; i4++) ivector2[ i4 ] = j * k; movl $np, 4(%esp) movl %eax, (%esp) call __isoc99_scanf movl $0, i4 jmp .L14 .L15: movl i4, %edx movl j, %eax movl k, %ecx imull %ecx, %eax movb %al, ivector2(%edx) movl i4, %eax addl $1, %eax movl %eax, i4 .L14: movl i4, %edx movl np, %eax cmpl %eax, %edx jle .L15 movl np, %edx movl $0, i4 movl j, %ecx xorl %ebx, %ebx movzbl k, %eax imull %ecx, %eax .L30: movb %al, ivector2(%ebx) addl $1, %ebx cmpl %ebx, %edx jge .L30 addl $1, %edx movl %edx, i4 Вычисление j*k вынесено из цикла.Вызов функции с аргументамиdead_code( 1, "This line should not be printed" ); movl $1, (%esp) call dead_code При оптимизации O2 функция dead_code не вызывается. Вызов функции без аргументовunnecessary_loop(); call unnecessary_loop.L37: movl j5, %eax movl $7, (%esp) movl $5, i movl %eax, k5Вызов функции был заменен её кодом. Что уменьшает время выполнения программы.Проверка недостижимого кода и лишних присваиванийvoid dead_code( a, b ) int a; char *b; { int idead_store; idead_store = a; if( 0 ) printf( "%s\n", b ); } pushl %ebp movl %esp, %ebp subl $16, %esp movl 8(%ebp), %eax movl %eax, -4(%ebp) pushl %ebp movl %esp, %ebp popl %ebp Недостижимый код не выполняется и не генерируется.Удаление ненужного циклаvoid unnecessary_loop() { int x; x = 0; for( i = 0; i < 5; i++ ) k5 = x + j5; } pushl %ebp movl %esp, %ebp subl $16, %esp movl $0, -4(%ebp) movl $0, i jmp .L20 .L21: movl j5, %eax addl -4(%ebp), %eax movl %eax, k5 movl i, %eax addl $1, %eax movl %eax, i .L20: movl i, %eax cmpl $4, %eax jle .L21movl j5, %eax pushl %ebp movl %esp, %ebp movl $5, i movl %eax, k5 popl %ebpВыполняется удаление ненужного цикла, поскольку тело цикла не зависит от i. Объединение циклов void loop_jamming( x ) int x; { for( i = 0; i < 5; i++ ) { k5 = x + j5 * i; printf(%d, k5); } for( i = 0; i < 5; i++ ) { i5 = x * k5 * i; printf("%d", i5); } } pushl %ebp movl %esp, %ebp movl $0, i jmp .L24 .L25: movl j5, %edx movl i, %eax imull %edx, %eax addl 8(%ebp), %eax movl %eax, k5 movl i, %eax addl $1, %eax movl %eax, i .L24: movl i, %eax cmpl $4, %eax jle .L25 movl $0, i jmp .L26 .L27: movl k5, %eax movl %eax, %edx imull 8(%ebp), %edx movl i, %eax imull %edx, %eax movl %eax, i5 movl i, %eax addl $1, %eax movl %eax, i .L26: movl i, %eax cmpl $4, %eax jle .L27 popl %ebp ---------------------------------------- отличается только вызовом printf pushl %ebp movl j5, %edx movl %esp, %ebp movl 8(%ebp), %eax movl $5, i popl %ebp leal (%eax,%edx,4), %edx sall $2, %eax imull %edx, %eax movl %edx, k5 movl %eax, i5 -------------------------------------- pushl %ebp xorl %eax, %eax movl %esp, %ebp pushl %ebx subl $20, %esp movl 8(%ebp), %ebx movl $0, i movl j5, %edx jmp .L21 .p2align 4,,7 .p2align 3 .L25: movl j5, %edx .L21: imull %edx, %eax movl $.LC0, 4(%esp) movl $1, (%esp) addl %ebx, %eax movl %eax, k5 movl %eax, 8(%esp) call __printf_chk movl i, %eax addl $1, %eax cmpl $4, %eax movl %eax, i jle .L25 movl $0, i movl k5, %edx xorl %eax, %eax jmp .L23 .p2align 4,,7 .p2align 3 .L26: movl k5, %edx .L23: imull %edx, %eax movl $.LC0, 4(%esp) movl $1, (%esp) imull %ebx, %eax movl %eax, i5 movl %eax, 8(%esp) call __printf_chk movl i, %eax addl $1, %eax cmpl $4, %eax movl %eax, i jle .L26 addl $20, %esp popl %ebx popl %ebpТак как конечные значения k5 и i5 можно посчитать без использования циклов, компилятор сразу присваивает переменной i значение равное 5 и подставляет его в формулы для k5 и i5. Следовательно, оптимизация объединения циклов не происходит из-за отсутствия циклов. Сохраним циклы путем добавления функций вывода значений k5 и i5 при каждой итерации. Оптимизация объединения циклов не выполнена компилятором.Развертка цикловvoid loop_unrolling( x ) int x; { for( i = 0; i < 6; i++ ) ivector4[ i ] = 0; } pushl %ebp movl %esp, %ebp movl $0, i jmp .L30 .L31: movl i, %eax movw $0, ivector4(%eax,%eax) movl i, %eax addl $1, %eax movl %eax, i .L30: movl i, %eax cmpl $5, %eax jle .L31 popl %ebp pushl %ebp movl %esp, %ebp movw $0, ivector4 movw $0, ivector4+2 movw $0, ivector4+4 movw $0, ivector4+6 movw $0, ivector4+8 movw $0, ivector4+10 movl $6, i popl %ebp retРазвертка циклов выполняется. Цикл заменяется присваиваниями. Получаем линейный участок кода, что уменьшает время выполнения программы. Сжатие цепочки переходовint jump_compression( i, j, k, l, m ) int i, j, k, l, m; { beg_1: if( i < j ) if( j < k ) if( k < l ) if( l < m ) l += m; else goto end_1; else k += l; else { j += k; end_1: goto beg_1; } else i += j; return( i + j + k + l + m ); } pushl %ebp movl %esp, %ebp .L34: movl 8(%ebp), %eax cmpl 12(%ebp), %eax jge .L35 movl 12(%ebp), %eax cmpl 16(%ebp), %eax jge .L36 movl 16(%ebp), %eax cmpl 20(%ebp), %eax jge .L37 movl 20(%ebp), %eax cmpl 24(%ebp), %eax jge .L38 movl 24(%ebp), %eax addl %eax, 20(%ebp) jmp .L41 .L38: nop .L40: jmp .L34 .L37: movl 20(%ebp), %eax addl %eax, 16(%ebp) jmp .L41 .L36: movl 16(%ebp), %eax addl %eax, 12(%ebp) jmp .L34 .L35: movl 12(%ebp), %eax addl %eax, 8(%ebp) .L41: movl 12(%ebp), %eax movl 8(%ebp), %edx leal (%edx,%eax), %eax addl 16(%ebp), %eax addl 20(%ebp), %eax addl 24(%ebp), %eax popl %ebp pushl %ebp movl %esp, %ebp pushl %esi pushl %ebx movl 8(%ebp), %ecx movl 12(%ebp), %eax movl 16(%ebp), %edx movl 20(%ebp), %ebx movl 24(%ebp), %esi cmpl %ecx, %eax jg .L17 jmp .L11 .p2align 4,,7 .p2align 3 .L20: cmpl %ebx, %edx jge .L13 cmpl %esi, %ebx .p2align 4,,7 .p2align 3 jl .L19 cmpl %eax, %ecx .p2align 4,,5 jge .L11 .L17: cmpl %eax, %edx .p2align 4,,5 jg .L20 addl %edx, %eax cmpl %eax, %ecx .p2align 4,,3 jl .L17 .L11: addl %eax, %ecx leal (%ecx,%edx), %edx leal (%edx,%esi), %esi leal (%esi,%ebx), %ebx leal (%ebx,%eax), %eax popl %ebx popl %esi popl %ebp ret .p2align 4,,7 .p2align 3 .L13: addl %ebx, %edx lea (%ecx,%edx), %edx leal (%edx,%esi), %esi leal (%esi,%ebx), %ebx leal (%ebx,%eax), %eax popl %ebx popl %esi popl %ebp ret .p2align 4,,7 .p2align 3 .L19: leal (%ecx,%edx), %edx addl %esi, %ebx leal (%edx,%esi), %esi leal (%esi,%ebx), %ebx leal (%ebx,%eax), %eax popl %ebx popl %esi popl %ebpПроисходит сжатие цепочки переходов. Компилятор уменьшает колличество переходов (количество переходов в оптимизированном коде на 2 меньше, чем в коде до оптимизации).
Таблица 2. Оптимизации, отсутствующие в файле optbench.c
Исходный текст на СиНеоптимизированный код на ассемблереОптимизированный код на ассемблереРезультаты Оптимизация переходовint my_1(c) { if(a==b) c=1; if (a!=b) c=2; return c; }my_1: pushl %ebp movl %esp, %ebp movl a, %edx movl b, %eax cmpl %eax, %edx jne .L44 movl $1, 8(%ebp) .L44: movl a, %edx movl b, %eax cmpl %eax, %edx je .L45 movl $2, 8(%ebp) .L45: movl 8(%ebp), %eax popl %ebpmy_1: movl a, %eax cmpl b, %eax pushl %ebp movl %esp, %ebp setne %al movzbl %al, %eax addl $1, %eax popl %ebpВ отличие от неоптимизированного кода, во втором случае второй условный оператор отсутствует. Вместо этого используется один сложный условный оператор. Исключение общих операций из цикла.int my_2(int *myvector, int d3) { for (w=0; w<10; w++) d3=d3+myvector[w]+S; return (d3); } pushl %ebp movl %esp, %ebp movl $0, w jmp .L48 .L49: movl w, %eax sall $2, %eax addl 8(%ebp), %eax movl (%eax), %eax addl 12(%ebp), %eax addl $3, %eax movl %eax, 12(%ebp) movl w, %eax addl $1, %eax movl %eax, w .L48: movl w, %eax cmpl $9, %eax jle .L49 movl 12(%ebp), %eax popl %ebp pushl %ebp movl $1, %edx movl %esp, %ebp movl 12(%ebp), %eax movl 8(%ebp), %ecx pushl %ebx movl $0, w .p2align 4,,7 .p2align 3 .L24: movl (%ecx), %ebx addl $4, %ecx movl %edx, w addl $1, %edx cmpl $11, %edx leal 3(%eax,%ebx), %eax jne .L24 popl %ebx popl %ebpОптимизация не выполняется. Значение S прибавляется отдельно на каждом шаге цикла. Удаление лишних присваиваний x=0; y=5; x=y;movl $0, x movl $5, y movl y, %eax movl %eax, xmovl $5, y movl $5, xПроизведено удаление лишних присваиваний (т.к. данное присваивание не может изменить логику программы). Условно недостижимый код:int my_3(int d4) { if(d4<10) d4=100; return d4; u=239; printf("%d",u); } pushl %ebp movl %esp, %ebp cmpl $9, 8(%ebp) jg .L52 movl $100, 8(%ebp) .L52: movl 8(%ebp), %eax popl %ebp pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax cmpl $9, %eax jg .L28 movl $100, %eax .L28: popl %ebpКомпилятор избавляется от недостижимого кода в обоих случаях.
3.3 Примеры наиболее удачных и неудачных оптимизаций
1) Наиболее неудачной оптимизацией оказалось «Объединение циклов». Оптимизация не была выполнена, однако код стал неудобочитаемым.
) Наиболее удачной является оптимизация «Развертка циклов». Она заменила код с двумя переходами на линейный участок, что позволило уменьшить время выполнения программы. Также достаточно удачной оптимизацией является «Удаление недостижимого кода». Так как код, являющийся недостижимым, может быть достаточно большим. Удаляя данный код, мы уменьшаем объем исполняемого файла.
Таблица 3. Выполняемые оптимизации
3.4 Подбор опций компилятора
Таблица 4. Уровни оптимизации
Уровень оптимизацииОписание-O0 Отключает оптимизацию. Только переменные, обьявленные register, сохраняются в регистрах.-O(-O1) Включает оптимизацию. Пытается уменьшить размер кода и ускорить работу программы. Соответственно увеличивается время компиляции. При указании -O активируются следующие флаги: -fauto-inc-dec -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fguess-branch-probability -fif-conversion2 -fif-conversion -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fsplit-wide-types -ftree-bit-ccp -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time-O2 Оптимизирует еще больше. GCC выполняет почти все поддерживаемые оптимизации, которые не включают уменьшение времени исполнения за счет увеличения длины кода. Компилятор не выполняет раскрутку циклов или подстановку функций, когда вы указываете -O2. По сравнению с -O, эта опция увеличивает как время компиляции, так и эффективность сгенерированного кода. -O2 включает все флаги оптимизации наследованные от -O. Также включает следующие флаги оптимизации: -fthread-jumps -falign-functions -falign-jumps -falign-loops -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip-blocks -fdelete-null-pointer-checks -fdevirtualize -fexpensive-optimizations -fgcse -fgcse-lm -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-sra -foptimize-sibling-calls -fpartial-inlining -fpeephole2 -fregmove -freorder-blocks -freorder-functions -frerun-cse-after-loop -fsched-interblock -fsched-spec -fschedule-insns -fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-switch-conversion -ftree-tail-merge -ftree-pre -ftree-vrp-O3 Оптимизирует еще немного. Включает все оптимизации -O2 и также включает флаги: -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-vectorize -fvect-cost-model -ftree-partial-pre -fipa-cp-clone -Os Включает оптимизацию по размеру. -Os флаг активирует все флаги оптимизации из -O2, в основном те, которые не увеличивают размер выходного файла. В дальнейшем выполняются оптимизации по уменьшению размера кода. -Os выключает следующие флаги оптимизации: -falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays -ftree-vect-loop-version
Рассмотрим некоторые флаги оптимизации и их комбинации для конкретной программы. В качестве программы используем линейный конгруэнтный генератор случайных чисел (файл lkg.c). А в качестве применяемых оптимизаций возьмем флаги, представленные в таблице ниже (Таблица 5. Рассматриваемые флаги оптимизации).
Таблица 5. Рассматриваемые флаги оптимизации
Оптимизация (флаги)Описание-fomit-frame-pointerОпция, которая говорит, что для доступа к переменным нужно использовать стек. С этой опцией практически невозможна отладка.-funroll-loopsВыполняется оптимизация развертыванием циклов. Осуществляется для циклов, число итераций которых может быть определено во время компиляции или во время выполнения.-funroll-all-loopsВыполняется оптимизация развертыванием циклов. Развертывает все циклы - обычно программы, скомпилированные с этой опцией, медленнее запускаются. -ffast-mathЭта опция разрешает GCC нарушать некоторые ANSI или IEEE правила и/или спецификации в интересах оптимизации кода по скорости выполнения. Например, это позволяет компилятору предполагать, что параметры к функции sqrt - неотрицательные числа.-fno-signed-zerosРазрешить оптимизации для арифметики с плавающей точкой, которые игнорируют знаковость нуля.
Данные флаги будем применять для рассматриваемой программы.
Будем находить время исполнения программы для каждого флага оптимизации или комбинации флагов. Также для каждой оптимизации найдем размер кода.
ОптимизацияВремя выполнения программы, мсРазмер кода, байт-O0 3795611343-O1 189034300-O2 188314212-O3 187484743-Os 190273994
Таблица 1.1. Флаги для оптимизации уровня -O0
Опции компилятораВремя выполнения программы, мсРазмер кода, байт-O0 3795611343-O0 -ffast-math3794310990-O0 -fomit-frame-pointer3667811292-O0 -funroll-loops3784811343-O0 -funroll-all-loops3826811343-O0 -fno-signed-zeros3792611343-O0 -ffast-math -fomit-frame-pointer3659910939-O0 -ffast-math -fomit-frame-pointer -funroll-loops3651710939-O0 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops3650810939-O0 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops -fno-signed-zeros3667310939
Таблица 1.2. Флаги для оптимизации уровня -O1
Опции компилятораВремя выполнения программы, мсРазмер кода, байт-O1 189034300-O1 -ffast-math188324377-O1 -fomit-frame-pointer188904347-O1 -funroll-loops1738111642-O1 -funroll-all-loops1735611642-O1 -fno-signed-zeros188434320-O1 -ffast-math -fomit-frame-pointer188014015-O1 -ffast-math -fomit-frame-pointer -funroll-loops1750011663-O1 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops1759711663-O1 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops -fno-signed-zeros1742311663
Таблица 1.3. Флаги для оптимизации уровня -O2
Опции компилятораВремя выполнения программы, мсРазмер кода, байт-O2 188314212-O2 -ffast-math188084280-O2 -fomit-frame-pointer188234161-O2 -funroll-loops1688017209-O2 -funroll-all-loops1688817209-O2 -fno-signed-zeros188694212-O2 -ffast-math -fomit-frame-pointer189794229-O2 -ffast-math -fomit-frame-pointer -funroll-loops1698817226-O2 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops1686717226-O2 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops -fno-signed-zeros1688317226
Таблица 1.4. Флаги для оптимизации уровня -O3
Опции компилятораВремя выполнения программы, мсРазмер кода, байт-O3 187484743-O3 -ffast-math18811 4787-O3 -fomit-frame-pointer188414692-O3 -funroll-loops1694317743-O3 -funroll-all-loops16913 17743-O3 -fno-signed-zeros18915 4743-O3 -ffast-math -fomit-frame-pointer187344736-O3 -ffast-math -fomit-frame-pointer -funroll-loops16873 17736-O3 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops16851 17736-O3 -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops -fno-signed-zeros16943 17736
Таблица 1.5. Флаги для оптимизации уровня -Os
Опции компилятораВремя выполнения программы, мсРазмер кода, байт-Os 190273994-Os -ffast-math190564062-Os -fomit-frame-pointer193164235-Os -funroll-loops190754300-Os -funroll-all-loops190824300-Os -fno-signed-zeros191014300-Os -ffast-math -fomit-frame-pointer187454312-Os -ffast-math -fomit-frame-pointer -funroll-loops188214312-Os -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops188034312-Os -ffast-math -fomit-frame-pointer -funroll-loops -funroll-all-loops -fno-signed-zeros187804312
4. Выводы по проделанной работе
Для рассматриваемой программы lkg.c самое быстрое время исполнения достигается при оптимизации -О3. Самый маленький код генерируется при флаге оптимизации -Оs. (что соответствует описанию соответствующих параметров оптимизации).
Исходя из Таблицы 1, можно сделать вывод, что компилятор GCC-4.4.3 выполняет практически все основные оптимизации из тестовой программы и, следовательно, является достаточно хорошим.
Используемая литература
http://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC