- •Int constant_operations() {
- •Какие выражения были вычислены на этапе компиляции?
- •Как изменилось количество инструкций?
- •Какие типы оптимизаций были применены?
- •Int dead_code_elimination() {
- •Int main_inline() {
- •Int dead_code_elimination() {
- •Int helper1(int a, int b) {
- •Int helper2(int X) {
- •Int main_inline() {
- •6) Вызов calculate из main
6) Вызов calculate из main
При -O2 и включённом inlining функция calculate может быть заинлайнена в main, так как её тело достаточно компактно.
Тогда компилятор знает конкретные значения base = 10, iterations = 5 и может частично или полностью свёрнуть цикл:
цикл от i = 0 до 4 — конечное количество итераций,
все вычисления могут быть выполнены в compile-time,
итоговая функция main может свестись к:
int main() {
printf("Result: %d\n", <константа>);
return 0;
}
Типы оптимизаций в этой программе:
Constant folding (100 * 50, (10 + 20) * 3 и возможные составные выражения).
Loop-invariant code motion (вынос invariant из цикла).
Strength reduction (i % 2 → i & 1).
Упрощение ветвлений (использование select).
Inlining (встраивание calculate в main).
Dead code elimination (удаление неиспользуемых временных переменных).
Упрощение CFG, сокращение числа переходов.
Рисунок 22 - Сгенерированный ассемблер с оптимизатором -O0
Рисунок 23 - Продолжение сгенерированного ассемблера с оптимизатором -O0
Рисунок 24 - Сгенерированный LLVM IR с оптимизатором -O0
Рисунок 25 - Продолжение сгенерированного LLVM IR с оптимизатором -O0
Рисунок 26 - Сгенерированный ассемблер с оптимизатором -O1
Рисунок 27 - Сгенерированный LLVM IR с оптимизатором -O1
Рисунок 28 - Сгенерированный ассемблер с оптимизатором -O2
Рисунок 29 - Продолжение сгенерированного ассемблера с оптимизатором -O2
Рисунок 30 - Продолжение сгенерированного ассемблера с оптимизатором -O2
Рисунок 31 - Сгенерированный LLVM IR с оптимизатором -O2
Рисунок 32 - Продолжение сгенерированного LLVM IR с оптимизатором -O2
Тексты программ: исходные и “оптимизированные”
5.1. Исходный вариант (все листинги одним файлом)
#include <stdio.h>
// Листинг 1
int constant_operations() {
int a = 10 + 20 * 5;
int b = (1 << 4) | 0x0F;
float c = 3.14 * 2.0 + 1.0;
return a + b + (int)c;
}
// Листинг 2
int dead_code_elimination() {
int x = 10;
int y = 20;
int z = x + y;
int unused1 = 100;
int unused2 = 200;
if (0) {
unused1 = 300;
}
return z;
}
// Листинг 3
int helper1(int a, int b) {
return a * b + 10;
}
int helper2(int x) {
return helper1(x, x + 1);
}
int main_inline() {
int result = 0;
for (int i = 0; i < 10; i++) {
result += helper2(i);
}
return result;
}
// Листинг 4
int simple_loop() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
int loop_with_invariant(int n) {
int result = 0;
int constant = 100 * 50;
for (int i = 0; i < n; i++) {
result += i + constant;
}
return result;
}
int nested_loops() {
int sum = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
sum += i * j;
}
}
return sum;
}
// Листинг 5
int calculate(int base, int iterations) {
int result = base;
if (iterations < 0) {
printf("This should never happen\n");
return -1;
}
for (int i = 0; i < iterations; i++) {
if (i % 2 == 0) {
result += i * 2;
} else {
result += i;
}
int invariant = 100 * 50;
result += invariant;
}
int final_adjustment = (10 + 20) * 3;
result += final_adjustment;
return result;
}
int main() {
int value = calculate(10, 5);
printf("Result: %d\n", value);
return 0;
}
5.2. Вариант “после оптимизации” (ручная эквивалентная оптимизация)
Это не обязательно совпадает 1:1 с реальным выводом Clang, но отражает те же идеи оптимизации.
#include <stdio.h>
// Листинг 1 – свёртка констант
int constant_operations_opt() {
// 10 + 20*5 = 110
// (1 << 4) | 0x0F = 31
// (int)(3.14*2.0 + 1.0) ≈ 7
return 110 + 31 + 7; // 148
}
// Листинг 2 – удаление мёртвого кода
int dead_code_elimination_opt() {
// unused1, unused2 и if (0) удалены
return 10 + 20; // 30
}
// Листинг 3 – инлайнинг
int main_inline_opt() {
int result = 0;
for (int i = 0; i < 10; i++) {
// helper2(i) = helper1(i, i + 1) = i*(i+1) + 10
result += i * (i + 1) + 10;
}
return result;
}
// Листинг 4 – циклы
// simple_loop: сумма от 0 до 99 заранее известна
int simple_loop_opt() {
return 4950;
}
int loop_with_invariant_opt(int n) {
int result = 0;
const int constant = 100 * 50; // 5000
for (int i = 0; i < n; i++) {
result += i + constant;
}
return result;
}
int nested_loops_opt() {
int sum = 0;
// те же вложенные циклы, но компилятор может частично развернуть
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
sum += i * j;
}
}
return sum;
}
// Листинг 5 – сложная программа
int calculate_opt(int base, int iterations) {
if (iterations < 0) {
// ветка не удаляется, т.к. зависит от аргумента
printf("This should never happen\n");
return -1;
}
int result = base;
const int invariant = 100 * 50; // 5000
const int final_adjustment = (10 + 20) * 3; // 90
for (int i = 0; i < iterations; i++) {
if ((i & 1) == 0) { // i % 2 == 0
result += i * 2;
} else {
result += i;
}
result += invariant;
}
result += final_adjustment;
return result;
}
int main_opt() {
int value = calculate_opt(10, 5);
printf("Result: %d\n", value);
return 0;
}
