В 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 ...;
менее читаем и менее эффективен чем
Поскольку свертывание констант
применяется к константам мно-
жественного типа также как к константам других типов, можно ис-
пользовать описания const без потери
эффективности.
Upper = ['A'..'Z'];
Lower = ['a'..'z'];
Alpha = Upper + Lower;
С учетом данных описаний оператор if генерирует тот же код,
что и в случае предыдущего оператор
if:
Для операций с
малыми множествами компилятор
генерирует
очень эффективный код. Малое множество - это множество с нижним
порядковым значением в диапазоне 0..7
и верхним порядковым значе-
нием в диапазоне 0..15. Например, следующие множества TByteSet и
TWordSet являются малыми множествами:
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,
который содержит ряд процедур и
функций. При этом программа редко
использует все эти процедуры. Если она
использует только одну или
две процедуры или функции, то только эти процедуры включаются в
полученный в результате код.