Pull to refresh
0

Для чего нужны оптимизирующие компиляторы?

Reading time 4 min
Views 14K
image


Сегодня практически все компиляторы, которые можно найти на рынке, являются оптимизирующими. Т.е. они не просто переводят написанный программистом код на машинный язык, но также могут улучшать его с точки зрения производительности или объема.

Но что конкретно может сделать компилятор для улучшения кода?

Прежде всего, следующие три вещи:
  1. компилятор может эффективно реализовать средства языка программирования
  2. может по максимуму задействовать возможности аппаратуры, на которой будет исполняться программа
  3. а также устранить некоторые недостатки реализованного программистом алгоритма.

Рассмотрим каждую из этих возможностей более подробно.

Современные языки программирования предоставляют программисту мощные средства для выражения алгоритма. Однако если эти средства слабо поддержаны на уровне компиляции, то удобства языков высокого уровня могут обернуться серьезными потерями производительности.

Рассмотрим в качестве примера вызов виртуальной функции в языке C++:
class A
{
    public:
        virtual int func () { return 5; }
};

 class B : public A
{
    public:
        virtual int  func () { <сложные вычисления>; }
}; 

A*  ptr = new A();

int value  = ptr->func();


Здесь мы имеем два класса: A и B. Класс A содержит простейшую виртуальную функцию, которая всегда возвращает константу (число «пять»). Класс B наследует класс A и при этом переопределяет виртуальную функцию, так что теперь она выполняет некоторые сложные вычисления.

Если механизм виртуальных функций поддержан в компиляторе слабо (Update: в данном месте не имеется в виду, что одни компиляторы поддерживают средства языка C++ более полно, чем другие. Имеется в виду, что одни компиляторы способны поддерживать эти средства с гораздо меньшими накладными расходами, чем другие. См. подробный разбор примера в комментариях к записи), то компилятор не сможет понять, какая именно версия виртуальной функции должна вызываться в выражении int value = ptr->func(); Однако хороший компилятор сможет определить, что вызывать нужно именно простейшую функцию (т.е. через указатель “ptr” адресуется только экземпляр класса A). С учетом сказанного, последнее выражение может быть оптимизировано следующим образом: int value = 5;

Приведенный пример также показывает, что нет смысла говорить об абсолютной эффективности некоторого языка программирования. Всегда должна подразумеваться конкретная реализация этого языка в компиляторе. Например, вопрос «Что быстрее, C++или Java?» не совсем корректен. Плохо реализованный C++ может проигрывать хорошей реализации Java, и наоборот хороший компилятор для C++ может давать огромную фору плохому компилятору Java.

Таким образом, мы видим, что мощь современных языков программирования в выражении алгоритмов оправдана только тогда, когда существуют эффективные реализации этих языков на уровне компиляции.

Рассмотрим теперь вторую возможность, которую предоставляют программисту оптимизирующие компиляторы, а именно возможность максимально эффективно задействовать аппаратные ресурсы.

Обратимся еще раз к современным языкам программирования. Такие языки, как C++, Java, Pascal и многие другие, позволяют программисту максимально сосредоточиться на записи алгоритма. Во многом это достигается за счет того, что язык «скрывает» от программиста особенности аппаратуры, на которой будет исполняться программа. Как можно более полное задействование аппаратных ресурсов как раз и является задачей оптимизирующего компилятора.

Отметим, что проблема эффективного распределения аппаратных ресурсов не является лишь следствием использования языков высокого уровня. Даже если программист пишет на языке ассемблера и при этом великолепно знаком с особенностями «железа», для которого он пишет, существуют задачи, которые очень тяжело решить без использования автоматических средств.

В качестве примера рассмотрим конвейеризацию циклов, существующую в микропроцессорах семейства Intel Itanium. Благодаря конвейеризации, на платформе Itanium возможно параллельное исполнение нескольких итераций одного цикла (см. рисунок).

Intel Itanium
Software Pipelining

Здесь в момент времени t1 начинается исполнение первой итерации некоторого цикла. В момент времени t2, еще в процессе исполнения первой итерации, стартует исполнение второй итерации того же цикла. А с момента времени t3 одновременно исполняются уже три итерации одного и того же цикла.

Планирование ресурсов для такой конвейеризации является довольно сложной «механической» задачей. Даже если программист является докой в программировании на ассемблере, ему нет никакой нужды тратить неимоверное количество своего времени на задачу, с которой оптимизирующий компилятор справится за доли секунды.

Перейдем, наконец, к третьему достоинству оптимизирующих компиляторов. Такие компиляторы могут вносить «штрихи» в алгоритмы программистов. Причем иногда довольно значимые «штрихи».

Конечно, компилятор не сможет радикально изменить алгоритм (по крайней мере, современные компиляторы еще очень далеки от этого), однако убрать некоторые неоптимальности ему вполне по силам. Иногда устранение таких неоптимальностей может приводить к ускорению программы в разы.

Рассмотрим пример.

for (i = 0; i < n; i++)
{
    с = e * f;
    a[i] = c * i;
}


В этом фрагменте C-кода на каждой итерации цикла производится вычисление переменной «c». При этом ни переменная «e», ни переменная «f» в цикле не изменяются. Поэтому нет никакой нужды вычислять переменную «c» в цикле. Достаточно один раз вычислить ее вне цикла:

с = e * f;

for (i = 0; i < n; i++)
{
    a[i] = c * i;
}


Такой вариант цикла будет работать значительно быстрее предыдущего.

Показанную выше трансформацию сможет выполнить даже самый простейший оптимизирующий компилятор. Однако можно привести примеры гораздо более сложных трансформаций, доступных только лучшим представителям семейства компиляторов.

Приведенные в тексте примеры показывают, что оптимизирующие компиляторы призваны выполнить за программиста ту часть работы, к выполнению которой было бы нерационально привлекать дорогостоящие человеческие ресурсы. При этом чем более надежен и «умен» компилятор, тем большую пользу можно извлечь из труда программиста.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+24
Comments 38
Comments Comments 38

Articles

Information

Website
www.intel.ru
Registered
Founded
Employees
5,001–10,000 employees
Location
США
Representative
Анастасия Казантаева