Съдържание
Съвременните компилатори са много „по-умни“, отколкото обикновено предполагаме. Те избавят програмиста от много досадна работа. Само преди 6–7 години това не беше така. Интелигентността на днешните компилатори позволява да пишем четливи и прегледни програми, без това да се отразява на ефективността. Но за да умеем да се възползваме от уменията на компилаторите, ние трябва да ги познаваме. Езикът Си е архаичен, от ниско ниво и подтиква програмистите сами да оптимизират програмите си. В действителност често тези опити само затрудняват компилаторите и влияят отрицателно на ефективността.
Оптимизационните преобразования, извършвани от съвременните компилатори можем да разделим на локална, глобална и междупроцедурна оптимизация.
Локалната оптимизация се осъществявя в рамките на линеен участък от програмата — без условни оператори, без цикли. Основната задача тук е да се разпределят променливите и междинните резултати от изчисленията по оптимален начин между регистрите на процесора и оперативната памет. Всъщност в оптимизираната програма променливите съществуват само условно. Една и съща променлива може веднъж да се намира в един регистър, друг път в друг, трети път да бъде в оперативната памет (напр. стека), а понякога може и изобщо да не съществува, ако компилаторът е преценил, че стойността й няма да потрябва. Регистрите съдържат това, което е най-потребно; понякога това могат да бъдат дори масиви!
Друг вид локална оптимицация е в премахването на многократни изчисления на едно и също нещо. Ако един и същи израз се среща многократно, стойността му ще бъде изчислена само веднъж.
Пример 2.1. Обединяване на общи операции
Няма нужда да опростяваме следната последователност от оператори:
a [i] = (i+1)/2 + 1; b [i] = (i+1)/2 + 2;
Компилаторът автоматично ще ги опрости и ще генерира код за следния оператор:
b [i] = (a[i] = (i+1)/2 + 1) + 1;
Пример 2.2. Еднократно смятане отместването в масив
Изразът
a [i] = a [i]/(a [i] + 1) * (a [i] - 1);
се свежда до
{int *x = a+i; int y = *x; *x = y/(y+1) * (y-1);}
Друг вид оптимизация, която правят компилаторите, е свеждането на аритметичните операции до по-прости. Няма смисъл сами да опростяваме аритметичните операции единствено с цел по-добра ефективност. Ефективността на преобразуванията, които ще направим, зависи до голяма степен от свойствата на използвания процесор. И може да се окаже, че когато компилираме програмата на процесор от друга фамилия (или от същата фамилия, но по-нов), тя ще работи по-бавно, отколкото ако не я бяхме „оптимизирали“ ръчно. По-добре да оставим тези оптимизации за компилатора, в повечето случаи той знае по-добре от нас свойствата на процесора.
Пример 2.3. Замяна на аритметични операции с по-ефективни
Изразът x*2 при процесори 80386 е най-добре да се опрости като x<<1. Но при всички по-нови процесори от тази фамилия по-бърз вариант се оказва x+x.
Можем да се доверим на уменията на съвременните компилатори — дори човек, програмиращ на Асемблер, с много труд би генерирал по-добър код. Някои компилатори се справят перфектно. ГНУ-компилаторът макар и не перфектен също се справя забележително добре, като се вземе предвид, че той не е оптимизирван за конкретен тип процесор, а може да генерира код за над 30 вида архитектури, повечето от които се разделят на няколко подтипа.