Copyright (C) А.Гавва V-0.4w май 2004

5. Оптимизация проекта

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

Если программа выполняется не так быстро как это ожидалось или если программа использует пространство памяти или диска размер которого превышает ожидаемый, то для улучшения характеристик результирующей программы, в первую очередь, необходимо пересмотреть дизайн исходных текстов программы. При этом, в первую очередь, необходимо убедиться в правильности выбора используемых структур данных и алгоритмов. Например, алгоритм пузырьковой сортировки является легким способом сортировки данных относительно маленького объема, однако алгоритм квиксорт (quick sort) обладает производительностью, которая во много раз выше.

В большой программе, не так легко определить подпрограмму скорость выполнения которой оказывает критическое влияние на общую производительность системы. Как правило, подобные подпрограммы называют узкими местами. Эксперименты с различными наборами входных тестовых данных и анализ времени выполнения различных участков программы позволяет отыскать такие узкие места. Для облегчения процесса поиска, можно прибегнуть к помощи специальных средств профилирования работы программы (например, использовать программу GNU gprof), которые позволяют автоматизировать процесс сбора статистики выполнения различных участков программы.

Некоторая оптимизация может быть выполнена компилятором GNAT автоматически. Для оптимизации программы можно использовать как опции командной строки, так и директивы компилятора.

5.1 Опции оптимизации компилятора

Существует несколько опций командной строки компилятора которые могут быть использованы для общей оптимизации программы:

-O0  -  Отсутствие оптимизации. Выполняет самую быструю компиляцию, а при наличии директив управляющих оптимизацией в исходном тексте программы GNAT будет выдавать предупреждающие сообщения. Рекомендуется для случаев когда важна быстрота компиляции.
-O или -O1  -  Обычная оптимизация, которая устанавливается по умолчанию. Это ведет к более медленной компиляции и отсутствию предупреждающих сообщений о наличии директив управляющих оптимизацией в исходном тексте программы. Как правило, вам необходимо использовать этот режим оптимизации.
-O2  -  Экстенсивная оптимизация, позволяющая получить исполнимый файл меньшего размера.
-O3  -  Полная оптимизация, с выполнением автоматической встроенной подстановки (inline) для подпрограмм и циклов маленького размера. Позволяет получить наиболее быстро исполняемый код.

При использовании вещественных чисел с плавающей точкой, можно сознательно округлять ошибки если не используется опция командной строки компилятора -ffloat-store. При этом следует учитывать, что согласно замечаний в GCC FAQ округление вещественных чисел с плавающей точкой может вызвать проблемы при использовании опций командной строки компилятора -O2 и -O3 без одновременного использования опции -ffloat-store (сохранение вещественных чисел с плавающей точкой вне регистров процессора, что замедляет выполнение программы).

На производительность результирующей программы оказывают влияние опции командной строки компилятора которые управляют встроенной подстановкой (inline):

-gnatn  -  позволяет осуществлять встроенную подстановку между пакетами, когда директива компилятора Inline используется в спецификациях пакетов.
-gnatN  -  позволяет осуществлять автоматическую встроенную подстановку между пакетами (ведет к большему расходу памяти)
отсутствие
-gnatn / -gnatN
 -  встроенная подстановка между пакетами не осуществляется даже в случаях, когда директива компилятора Inline используется в спецификациях пакетов.

Следует заметить, что использование этих опций требует одновременного использования опции -O, иначе эти опции не приведут к ожидаемому результату.

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


pragma Suppress (All_Checks);

Для общей оптимизации программы могут быть также использованы следующие опции gcc (gnatgcc):

-ffast-math  -  GCC будет игнорировать некоторые требования математических правил ANSI и IEEE. Например, в результате применения этой опции, перед вызовом функции sqrt не будет выполняться проверка того, что число имеет не отрицательное значение.

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

-fomit-frame-pointer  -  GCC будет освобождать регистры, которые обычно предназначены для сохранения указателя на кадр стека.

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

5.2 Средства оптимизации GNAT, используемые в исходном тексте

Существует несколько директив компилятора, которые позволяют изменять размер и скорость выполнения программы:

Директива    Описание
pragma Pack( Aggregate );  -  Использовать минимальный размер пространства для агрегата.
pragma Optimize( Space / Time / Off );  -  Выбор типа оптимизации инструкций.
pragma Inline( Subprogram );
pragma Inline_Always( Subprogram );
 -  Указывают на необходимость выполнения встроенной подстановки (inline) подпрограммы Subprogram.
pragma Discard_Names( type );  -  Не помещать ASCII-идентификаторы в результирующий исполняемый файл.

Директива Pack позволяет упаковывать массивы, записи и тэговые записи, что позволяет им, в результате, занимать меньшее пространство. Например, упакованный массив переменных логического типа Boolean приводит к тому, что каждая переменная занимает всего один бит. Директива Pack позволяет упаковывать только структуры данных. Следует также учесть, что не каждый самостоятельный элемент структуры данных может быть упакован. Например, если имеется массив записей, то для того чтобы при распределении пространства использовался минимально возможный размер, понадобиться выполнить как упаковку массива, так и упаковку записи. Также следует учитывать, что упаковка структур данных, как правило, ухудшают скорость выполнения программы. Примером использования этой директивы компилятора для упаковки записи может служить следующее:


type CustomerProfile is
    record
        Preferred         : Boolean;
        Preorders_Allowed : Boolean;
        Sales_To_Date     : Float;
    end record;
pragma Pack( CustomerProfile );

Следует заметить, что GNAT способен выполнять достаточно плотную упаковку, упаковывая отдельные компоненты структур вплоть до индивидуальных битов.

Директива компилятора Optimize позволяет указать компилятору требуемый тип оптимизации инструкций: для максимально возможной скорости выполнения инструкций (Time), для использования инструкциями минимально возможного размера (Space) или без выполнения какой-либо оптимизации вообще (Off). Эта директива никак не воздействует на структуры данных.


pragma Optimize ( Space );
package body AccountsPayable is

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


procedure Increment( X : in out Integer ) is
begin
    X := X + 1;
end Increment;
pragma Inline( Increment );

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

Директива компилятора Inline_Always вызывает принудительную встроенную вставку подпрограмм, описанных в разных пакетах (подобно опции -gnatn) не зависимо от указания в командной строке компилятора опций -gnatn или -gnatN.

Директива компилятора Discard_Names позволяет освободить пространство занимаемое ASCII-строками имен идентификаторов. Например, при наличии большого перечислимого типа, Ада обычно сохраняет строки имен для каждого идентификатора значения перечислимого типа. Это выполняется для поддержки использования атрибута 'Img. Если использование атрибута 'Img не планируется, то можно указать компилятору на необходимость очистки этих имен.


type Dog_Breed is (Unknown, Boxer, Shepherd, Mixed_Breed);
pragma Discard_Names( Dog_Breed );

Примечательно, что при выполнении очистки имен, атрибут 'Img остается доступным. В этом случае, вместо возвращения строкового представления имени идентификатора, атрибут 'Img будет возвращать позицию значения в списке перечисления значений перечислимого типа (например, 0, 1, 2 и так далее).

5.3 Оптимизация для специфического типа процессора

Для GCC версий 2.x существует две основные опции оптимизации, которые основаны на специфическом типе процессора. Это указано в руководстве по GCC:

-mno-486  -  оптимизация для 80386.
-m486  -  оптимизация для 80486. Однако, такая программа сможет выполняться на процессоре 80386.

Следует заметить, что в настоящий момент для GCC версий 2.x, нет опций поддержки новых типов процессоров фирмы Intel (Pentium и более новые). Однако предполагается, что будущие версии GNAT, которые будут собраны с использованием GCC версий 3.x и более новыми версиями GCC, возможно, будут полноценно поддерживать следующие опции:

-mpentium  -  оптимизация для Pentium / Intel 586.
-mcpu=i686  -  оптимизация для Pentium II/ Intel 686.
-mcpu=k6  -  оптимизация для AMD K6.

Для GCC 2.8.1, который используется совместно с GNAT, рекомендуется следующая комбинация опций компилятора для получения разумного выигрыша в производительности при использовании процессора Pentium:


-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strength-reduce

Существуют также другие опции, которые могут быть полезны или бесполезны, в зависимости от конкретной программы. Для получения более полной информации следует обратиться к gcc FAQ.

Рассмотрим совместное использование этих опций. Предположим, что необходимо разработать программу которая будет выполняться на процессоре Intel Pentium, и скорость выполнения программы имеет существенное значение. В процессе разработки программы, можно использовать утилиту gnatmake с опцией -O1. Такая установка будет подавлять предупреждающие сообщения об использовании директив оптимизации в исходном тексте. После завершения разработки, для выполнения заключительной сборки проекта, можно использовать следующую комбинацию опций для утилиты gnatmake:


-m486 -O3 -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strength-reduce -gnatp

В результате, это позволяет обеспечить максимальную производительность при выполнении программы на процессоре Intel Pentium.


Copyright (C) А.Гавва V-0.4w май 2004