Глава 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е,
вторая проверка не вычисляется.
Противоположным вычислению по короткой схеме является полное
вычисление, которое можно выбрать с помощью директивы компилятора
{$В+}. В этом случае обеспечивается вычисление каждого операнда
булевского выражения.
Параметры-константы
Там, где это возможно, вместо параметров-значений следует
использовать параметры-константы. Параметры-константы настолько
же эффективны, что и параметры-переменные, а во многих случаях
превосходит их по эффективности. В частности, параметры-константы
генерируют получение кода меньшего размера и программы с ними вы-
полняются быстрее, чем программы с параметрами-значениями струк-
турного и строкового типов.
Параметры-константы более эффективны, чем параметры-значе-
ния, поскольку компилятору не приходится генерировать копии фак-
тических параметров на входе в процедуры и функции. Значения па-
раметров должны быть скопированы в локальные переменные, так что
модификации формальных параметров не приведут к модификации фак-
тических параметров. Поскольку формальные параметры-константы мо-
дифицироваться не могут, компилятору нет необходимости копировать
фактические параметры, что экономит код и пространство в стеке.
Устранение избыточной загрузки указателей
В определенных ситуациях генератор кода 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.
По умолчанию в состоянии {$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;
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,
который содержит ряд процедур и функций. При этом программа редко
использует все эти процедуры. Если она использует только одну или
две процедуры или функции, то только эти процедуры включаются в
полученный в результате код.
Назад | Содержание | Вперед