Pull to refresh
202.29
Rating

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

Intel corporate blog
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:компиляторcompiler
Hubs: Intel corporate blog
Total votes 44: ↑34 and ↓10 +24
Views13K

Comments 38

Only those users with full accounts are able to leave comments. Log in, please.

Top of the last 24 hours

Information

Founded
Location
США
Website
www.intel.ru
Employees
5,001–10,000 employees
Registered
Representative
Виктор Гурылев

Habr blog