В 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,  который содержит ряд процедур и функций. При этом программа редко  использует все эти процедуры. Если она использует только одну или  две процедуры или функции,  то только эти процедуры включаются  в  полученный в результате код.