Глава 23. Автоматическая оптимизация

             В Borland Pascal выполняется несколько различных типов опти-

        мизации  кода,  начиная  от свертывания констант и вычисления бу-

        левских выражений по короткой схеме и кончая  эффективной  компо-

        новкой. Рассмотрим некоторые виды оптимизации.

Свертывание констант

             Если участвующие  в  операции  операнды  представляют  собой

        константы перечислимого типа, то в Borland такое выражение вычис-

        ляется во время компиляции. Например, выражение:

 

             Х := 3 + 4 * 2

 

        приведет к генерации такого же кода, как выражение Х := 11, а вы-

        ражение:

 

             S := 'In' + 'Out'

 

        генерирует тот же код, что S := 'InOut'.

 

             Аналогично, если операнды функций Abs, Sqr, Succ, Pred, Odd,

        Lo, Hi и Swap представляют собой константы перечислимого типа, то

        функция вычисляется во время компиляции.

 

             Если индексом массива является константа или выражение, сос-

        тоящее из констант, то адрес элемента вычисляется во время компи-

        ляции.  Например,  доступ к элементу Dаtа[5,5] так же эффективен,

        как доступ к простой переменной.

Слияние констант

             Использование одной и той же строковой константы два или бо-

        лее раз приводит к генерации только одной копии константы. Напри-

        мер, два или более оператора Write('Dоnе') в одной и той же части

        программы приведет к ссылке на одну и ту же копию строковой конс-

        танты 'Donе'.

Вычисление по короткой схеме

             В Borland Pascal реализуется вычисление булевского выражения

        по короткой схеме.  Это означает, что вычисление булевского выра-

        жения прекращается, как только результат всего булевского выраже-

        ния  становится  очевидным.  При  этом обеспечивается минимальное

        время выполнения и,  обычно,  минимальный размер объектного кода.

        Вычисление  по  короткой  схеме делает также возможным вычисление

        конструкций, которые иначе были бы недопустимыми. Например:

             while (I<=Length(S)) and (S[I]<>' ') do

               Inc(I);

             while (P<>nil) and (P^.Value<>5) do

               P:=P^.Next;

 

             В обоих случаях,  если первая проверка имеет значение Falsе,

        вторая проверка не вычисляется.

 

             Противоположным вычислению по короткой схеме является полное

        вычисление, которое можно выбрать с помощью директивы компилятора

        {$В+}.  В этом случае обеспечивается вычисление каждого  операнда

        булевского выражения.

Параметры-константы

             Там, где это возможно,  вместо  параметров-значений  следует

        использовать  параметры-константы.  Параметры-константы настолько

        же эффективны,  что и параметры-переменные,  а во многих  случаях

        превосходит их по эффективности. В частности, параметры-константы

        генерируют получение кода меньшего размера и программы с ними вы-

        полняются быстрее,  чем программы с параметрами-значениями струк-

        турного и строкового типов.

 

             Параметры-константы более эффективны,  чем  параметры-значе-

        ния,  поскольку компилятору не приходится генерировать копии фак-

        тических параметров на входе в процедуры и функции.  Значения па-

        раметров должны быть скопированы в локальные переменные,  так что

        модификации формальных параметров не приведут к модификации  фак-

        тических параметров. Поскольку формальные параметры-константы мо-

        дифицироваться не могут, компилятору нет необходимости копировать

        фактические параметры, что экономит код и пространство в стеке.

Устранение избыточной загрузки указателей

ndif]>

             В определенных ситуациях генератор кода Borland Pascal может

        устранить избыточные инструкции загрузки указателей, уменьшая тем

        самым размер кода и обеспечивая  более  быстрое  его  выполнение.

        Когда генератор  кода  может  обеспечить,  что указатель остается

        постоянным на участке линейного кода (кода,  не содержащего пере-

        ходов на  него),  и когда указатель уже загружен в пару регистров

        (например, ES:DI),  генератор кода  устраняет  ненужные  загрузки

        указателей в блоке кода.

 

             Указатель считается константой,  если он получается из пара-

        метра-переменной (параметры-переменные всегда передаются как ука-

        затели) или из ссылки на переменную оператор with. Поэтому опера-

        тор with часто является более эффективным (но  никогда  не  будет

        менее эффективным),  чем запись для каждой ссылки на элемент пол-

        ностью уточненной переменной.

Подстановка констант множественного типа

             Когда правая  часть оператор in является константой множест-

        венного типа,  компилятор генерирует включение проверки с помощью

        команд CMP. Такие поставляемые проверки более эффективны чем код,

        генерируемый в противном случае соответствующими булевскими выра-

        жениями с использованием операций отношения. Например, оператор:

 

             if ((Ch >= 'A') and (Ch <: 'Z')) or

                  ((Ch >= 'a') and (Ch <= 'z')) then ...;

 

        менее читаем и менее эффективен чем

 

             if Ch in ['A'..'Z', 'a'..'z'] then ...;

 

             Поскольку свертывание констант применяется к константам мно-

        жественного типа  также как к константам других типов,  можно ис-

        пользовать описания const без потери эффективности.

 

             const

                Upper = ['A'..'Z'];

                Lower = ['a'..'z'];

                Alpha = Upper + Lower;

 

             С учетом  данных описаний оператор if генерирует тот же код,

        что и в случае предыдущего оператор if:

 

             if Ch in Alpha then ... ;

Малые множества

             Для операций  с  малыми  множествами  компилятор  генерирует

        очень эффективный код.  Малое множество - это множество с  нижним

        порядковым значением в диапазоне 0..7 и верхним порядковым значе-

        нием в диапазоне 0..15.  Например, следующие множества TByteSet и

        TWordSet являются малыми множествами:

 

             type

                TByteSet = set of 0..7;

                TWordSet = set of 0..15;

 

             Операции с  малыми  множествами,  такие как объединение (+),

        разность (-),  пересечение (*) и проверка на включение in генери-

        руют с помощью операций AND, OR, NOT и TEST вместо вызова библио-

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

        стандартные процедуры Include и Exclude генерируют при применении

        к малым множествам поставляемый код.

Порядок вычисления

             Стандартами Паскаля допускается,  что операнды  в  выражении

        часто вычисляются в порядке,  отличном от того, в котором они за-

        писаны (слева направо). Например, оператор:

 

             I := F(J) div G(J)

 

        где F и G - функции целого типа,  приводит к тому, что G вычисля-

        ется перед вычислением F, так как это позволяет компилятору полу-

        чить более оптимальный объектный код. Важно, поэтому, чтобы выра-

        жение  никогда  не  зависело  от  какого-то  конкретного  порядка

        вычисления встроенных функций.  Если вернуться к предыдущему при-

        меру, то для того, чтобы вызвать функцию F перед функцией G, мож-

        но использовать временную переменную:

 

             T := F(J);

             I := T div G(J);

 

             Исключением из этого правила является вычисление по короткой

        схеме (разрешенное директивой компилятора {$B-}, при котором опе-

        ранды булевского типа,  связанные операциями and или  оr,  всегда

        вычисляются слева направо.

Проверка на допустимость границ

             Присваивание константы  переменной и использование константы

        в качестве значения параметра проверяется во время  компиляции на

        допустимость нахождения в заданных границах. При этом генерирует-

        ся такой код, что во время выполнения таких проверок не делается.

        Например,  Х  :=  999,  где Х - переменная байтового типа (Bytе),

        приводит к ошибке компиляции.

Использование сдвига вместо умножения

             Операция X*C,  где C - константа,  являющаяся степенью числа

        2,  приводит к генерации объектного кода,  в котором используется

        инструкция Shl (сдвиг влево).

 

             Аналогично, когда  размерность  массива  представляет  собой

        степень числа 2,  то для вычисления индексных выражений использу-

        ется инструкция Shl (а не инструкция Мul).

Автоматическое выравнивание на границу слова

             По умолчанию Borland Pascal выравнивает все переменные и ти-

        пизованные константы,  превышающие по размеру 1 байт,  на границу

        машинного слова. На всех 16-разрядных процессорах семейства 80х86

        выравнивание на границу слова означает более быстрое  выполнение,

        поскольку  доступ к элементам размером в слово или четным адресам

        осуществляется быстрее, чем к словам по нечетному адресу.

 

             Выравнивание данных  управляется  директивой компилятора $A.

 

         B.Pascal 7 & Objects/LR     - 409 -

 

        По умолчанию в состоянии {$A+} переменные и типизованные констан-

        ты выравниваются указанным выше образом.  В состоянии {$A-} ника-

        ких действий по выравниванию не производится.

 

                   Примечание: Дальнейшие подробности приведены в Главе 2

              ("Директивы компилятора") "Справочного руководства програм-

              миста".

Удаление неиспользуемого кода

             Операторы, о которых известно,  что они никогда не будут вы-

        полняться,  не включаются в объектный код. Данные выражения, нап-

        ример, не приведут к генерации объектного кода:

 

             if false then

                  statement         { оператор }

              while false do

                  statement

Эффективная компоновка

             Компоновщик Borland Pascal автоматически удаляет неиспользу-

        емый код (по процедурам), то есть процедуры и функции, являющиеся

        частью скомпилированной программы, но к которым нет обращений, не

        включаются в файл типа .EXE. Процедуры, функции, переменные и ти-

        пизованные константы, участвующие в процессе компиляции, но ссыл-

        ки на которые отсутствуют,  удаляются из файлa .EXE. Удаление не-

        используемого кода выполняется по процедурам,  а  удаление  неис-

        пользуемых данных - по секциям, где эти данные описываются.

 

             Рассмотрим следующую программу:

 

             program SmartLink;

             const

                H: array[0..15] of char = '0123456789ABCDEF';

             var

                I,J : integer;

                X,Y : real;

             var

                S: string[79];

             var

                A: array[1..10000] of integer;

 

             procedure P1:

             begin

               A[1] = 1;

             end;

 

             procedure P2;

             begin

                I := 1;

 

         B.Pascal 7 & Objects/LR     - 410 -

 

             end;

 

             procedure P3;

             begin

                S := 'Borland Pascal';

                P2;

             end;

 

             begin

                P3;

             end;

 

             Основная программа вызывает процедуру P3,  которая  вызывает

        процедуру  P2,  поэтому  обе  процедуры P2 и P3 включаются в файл

        .EXE.  Поскольку P2 ссылается на первый раздел описания  перемен-

        ных,  а P3 ссылается на второй раздел описание переменных,  пере-

        менные I,  J, X, Y, S также включаются в выполняемый файл. Однако

        на  процедуру  P1 никаких ссылок нет,  а включенные в выполняемый

        файл процедуры не ссылаются на переменные Н и A, поэтому эти объ-

        екты удаляются.

 

             Эффективная компоновка  имеет  особую ценность в связи с ис-

        пользованием модулей,  которые реализуют  библиотеки  процедур  и

        функций.  Примером такого модуля является стандартный модуль Dos,

        который содержит ряд процедур и функций. При этом программа редко

        использует все эти процедуры. Если она использует только одну или

        две процедуры или функции,  то только эти процедуры включаются  в

        полученный в результате код.