Методи побудови інтерпретаторів. Покрокові компілятори

advertisement
Тема 13. Задачі оптимізації програм. Оптимізація лінійних ділянок. Згортки.
Виключення зайвих операцій. Поміркована оптимізація циклів. Побудова й
аналіз графа програми. Винесення інваріантних операцій. Заміна
перевірки. Виключення вироджених присвоєнь. Розподіл регістрів.
Методи побудови інтерпретаторів. Покрокові компілятори. Віртуальні
машини. Механізм макрогенерації в процесі компіляції
Различают машинно-зависимую и машинно-независимую оптимизацию.
Машинно-зависимая оптимизация в простейшем виде рассматривалась при
генерации кода и связана с сокращением числа используемой памяти под временные
переменные. Другой прием состоит в оптимизации после того, как сгенерированы машинные
команды. Предполагается, что компьютер располагает R1, R2, … Rn регистрами в таком
количестве, что их достаточно для выполнения всех необходимых вычислений адресов. После
генерации кода n-условных регистров отображаются на m-параллельно существующих. В
нужных местах программы вставляются команды типа “память→регистр” и “регистр→память”.
Исходя из этих операций, выделяется память для временных переменных.
При сокращении числа временных переменных необходимо контролировать зону
существования переменной. Зона определяется первой командой записи в переменную и
последней командой чтения в результате просмотра тетрад.
Пример:
E:=A*B+C*D;
F:=A*B+1;
G:=C*D;
Для этого набора операторов последовательность тетрад после машинно-независимой
оптимизации выглядит так (определяется, что C*D и A*B должны вычисляться один раз):
(1) (*, A, B, T1);
(2) (8, C, D,T2);
(3) (+, T1, T2, T3);
(4) (:=, T3, , E);
(5) (+, T1, 1, T5);
(6) (:=, T5, , F);
(7) (:=, T2, ,G);
Здесь зоны для T1,T2,T3 и T5 следующие:
Переменная
Зона существования
T1
(1)÷(5)
T2
(2)÷(7)
T3
(3)÷(4)
T4
(5)÷(6)
В алгоритме генерации машинных команд эта информация используется следующим
образом. В таблицу имен заносится информация о зоне существования временной
переменной. Если в течение всего перевода зоны переменной в машинные команды временная
переменная оставалась на сумматоре или регистре, то память для нее не выделяется.
Если в какой-нибудь момент содержимое сумматора или регистра нужно запомнить, то
только в этом случае программа выделит память для этой временной переменной и
сгенерирует команды, выполняющие пересылку. После генерации машинных команд для
тетрады проверяется каждый операнд, не является ли он временной переменной, зона
существования которой оканчивается этой тетрадой. Если это так, то выделенная для этой
переменной ячейка освобождается и поступает в общий пул. Но может быть, конечно, что
память не выделилась, т.е. не требуется разгрузить регистр.
Рассмотрим, как хранится информация в зонах и перераспределяются ячейки. Укажем
три способа хранения информации о зоне.
1. В ТИ в описателе временной переменной подсчитывается число ее появлений в тетрадах.
При генерации тетрад или во время машинно-независимой оптимизации формируются
значения счетчиков. Когда генерируются машинные команды, при появлении имени
временной переменной в тетраде счетчик уменьшается. Как только счетчик обнуляется,
адрес выделенной переменной может перераспределяться (возможно, адрес и не
выделялся).
2. В ТИ в описателе временной переменной, сохраняется номер последней тетрады, где
используется эта переменная.
3. В ТИ в описателе временной переменной выделяются поля, соответствующие головному и
хвостовому указателям списка всех ссылок на данную переменную. Элементами списка
являются тетрады, где создаются ссылки к переменной. Как только выполняется тетрада
транслируется, соответствующие ей элементы исключаются из списков. При этом способе
любая тетрада должна располагать по крайней мере тремя парами указателей. Указатели
ссылаются в каждой паре вперед и назад.
Для перераспределения ячеек под временные переменные используется алгоритм,
предложенный Данцигом и Рейнольдсом в 1966 году. Перераспределение выполняется в
пределах активной области данных для линейного участка программы (без циклов).
Предполагается, что это перераспределение выполняется для переменных одного типа. При
желании можно модифицировать алгоритм так, чтобы приписывать сразу по несколько ячеек
любой переменной в соответствии с ее типом, или для любого объявленного в программе типа
определить свой пул ячеек.
Иногда на уровне машинно-независимой оптимизации пытаются выполнить
тождественные алгебраические преобразования, которые привели бы к уменьшению числа
операций. К ним относится исключение унарного минуса и оператора abs. Например, можно
выполнить преобразование:
- C * ( - ( A + B ) + D )  - C * ( - ( ( A + B ) –D ) )
--C*(A+B–D)
C*(A+B–D)
В этом случае сокращается число операций. Если выполнять эти преобразования во
время генерации машинных команд, то не всегда удается сгенерировать меньшее число
команд, необходимы дополнительные действия. Для любой временной переменной требуется
выделить дополнительное двухразрядное поле с указанием, какая операция отложена:
0  + /отсутствие операции/,
1  - / унарный минус/,
2  ABS,
3  - ABS.
При переводе тетрад вида (-, А,Ø , Т) или (ABS, A,Ø , T) генератор кода не генерирует
команды, но заполняет двухразрядное поле отложенной операции в описании Т в ТИ в
соответствии с таблицей и в зависимости от А:
Переменная Т - временная
переменная
и
ее
отложенные операции
+
–
ABS
-ABS
Переменная А
- переменная
исходной
программы
А, т.е. А либо временная переменная,
либо
переменная
пользователя
Тетрада
(-, А, , Т)
(ABS, A, , T)
–
ABS
+
ABS
-ABS
ABS
–
ABS
ABS
ABS
В описателе Т в ТИ предусматривается также ссылка на элемент ТИ ( в данном случае
на А), что его значение тоже, что и А, в том случае, если А не находиться в сумматоре или
регистре. Если обрабатывается тетрада (*Т1, Т2, Т3) и Т1, Т2, Т3 временные переменные, то
анализируется знаковые разряды в соответствии с таблицей. Если Т1 или Т2 переменные
исходной программы, то принимается, что их отложенные операции равны - + (ничего не
делать). Таблица для тетрады "*":
T1 \ T2
+
–
ABS
-ABS
Поле
+
++
–
отложенной
–
–
+–
операции для Т3
ABS
ABS ABS
-ABS
-ABS
-ABS
ABS -ABS
Если элемент матрицы не пуст, то генерируется обычная команда умножения, а в поле
отложенных операций для Т3 вписывается код из пересечения. Если элемент матрицы пуст, то
формируются команды для выполнения ABS для Т1 или Т2, и запоминания результата в Т1 или
Т2, вносится соответствие изменения в описатель в ТИ для Т1 и Т2 и повторно делается
попытка обработать эту же тетраду.
Тетрада
Генерация с отложенной операцией
Обычная генерация
(+, A, B, T1)
LOAD A
LOAD A
ADD B
ADD B
(ABS, T1, T2)
ABS
86
(*, C,D,T3)
(+, T2,T3,T4)
STORE T2
LOAD C
MULT D
STORE T3
LOAD T2
ABS
ADD T3
STORE T2
LOAD C
MULT D
ADD T2
!!!
Генерация команд для смешанных вычислений выражений со смешанными типами
операндов. Так, если А – вещественная переменная, а остальные целого типа, тетрады для
К:=A*I+J имеют вид:
(1) (CVIR I, , T1)
(1) (* A, I, T1)
(2) (*R A, T1, T2)
или
(2) (+ T1, J, T2)
(3) (CVIR J, , T3)
(3) (:= T2, ,K)
(4) (+R T2, T3, T4)
(5) (CVRI T4, , T5)
(6) (:=I T5, , K)
В первом случае - левая колонка, генератор тетрад заранее определяет типы
операндов и для этих типов команды:
 CVIR – преобразование из integer в real;
 CVRI – преобразование из real в integer;
 *R – умножение вещественных чисел;
 +R – сложение вещественных чисел;
 :=I – присваивание целому и т.д.
Нагрузка на генератор машинных команд уменьшается, ему не надо проверять типы
операндов; для *R он генерирует MULTF, по *I - MULT и т.д. Во втором случае (правая колонка),
увеличивается нагрузка на генератор машинных команд, он должен анализировать типы
операндов, генерировать, если необходимо, команды преобразования типов и т.д.
Рекомендуется первый подход, когда семантические программы генерации тетрад анализируют
типы операндов и сразу генерируют тетрады преобразования типов и тетрады с операциями в
точном соответствии с типами операндов, поскольку сегменты генерации машинных команд
могут быть достаточно сложными, если учесть необходимость отслеживания состояния
регистров и сегментирования пргораммы и данных.
Машинно-независимая оптимизация сводится к различным типам преобразований
исходной программы или ее промежуточных форм. Эти эвристические правила повышения
эффективности называют каталогом оптимизационных преобразований, который все еще
пополняется. Перечислим общепризнанные преобразования.
1. Исключение повторных вычислений; предусматривается исключение из выражений в
программе общих подвыражений (пример дан в предыдущей лекции для тетрад).
2. Замена медленно выполняемых операций быстрыми; например, замена операции
умножения на целое число операцией сложения, операции возведения в целую степень
операцией умножения, операции деления – на операцию умножения;
3. Исключение мертвых переменных, которые после определения их значений нигде в
программе не используются (для этого в ТИ можно для переменных завести флаги
использований переменных, учитывающих ссылки на них).
4. Распространение констант как замена переменных, значения которых известны,
константами в выражениях. Например, дано:
A=2;
B=3;
………
C=A*B;
………
T=(B+C)*A+W;
после распространения:
A=2;
B=3;
………
C=6;
………
T=18+W;
Здесь необходим контроль всех ветвей программы до операторов С=… и Т=… ..
87
5. Инициализация переменных как инициализация значений в объявлениях вместо
примененного в программе оператора присваивания; тогда начальное значение переменной
назначается при загрузке программы, а не в ходе вычислений;
6. Сокращение числа преобразований данных; выполняется за счет перегруппировки
выражений; например, если A+B+C+B+A+C, где A,B и C имеют разные атрибуты, записать в
виде (A+A)+(B+B)+(C+C), то можно сэкономить на преобразовании типов данных и выиграть в
точности представления результата.
7. Сокращение числа обращений к функциям; если в программе имеется несколько
обращений к некоторой функции с одинаковыми аргументами, то после первого обращения к
ней значение функции следует запомнить и использовать его в других точках программы без
обращения к функции. Например, если имеется:
A=X*TAN(x)+LOG(ABS(COS(x)));
B=(COS(x)+TAN(x))/TAN(x);
то необходимо выполнить преобразование:
T001=TAN(x);
T002=COS(x);
A=X*T001+LOG(ABS(T002));
B=(T002+T001)/T001;
8. Сокращение числа обращений к заиндексированным переменным; используется тот же
прием. Например, во фрагменте программы
Do i=1 To 100;
x(i)=y(i)+1/y(i);
z(i)=y(i)**2;
End;
можно сократить число обращений к переменной y(i).
9. Объединение циклов для сокращения времени и памяти на управление циклом.
Например:
Do i=1 To 100;
x(i)=0;
End;
Do j=1 To 200;
z(j)=y(j);
End;
После объединения имеем:
Do i=1 To 100;
x(i)=0;
z(j)=y(j);
End;
Do j=101 To 200;
z(j)=y(j);
End;
10. Развертывание цикла; можно сократить число операций приращения и проверки
завершения цикла. Пусть имеем:
Do i=1 To 100;
x(i)=0;
End;
При развертывании с шагом 2, имеем:
Do i=1 To 99 By2;
x(i)=0;
x(i+1)=0;
End;
11. Исключение из тела цикла инвариантных вычислений; вычисление называется
инвариантным в цикле, если ни один из его операндов не меняет своего значения внутри
цикла. Пусть, например, имеем:
Do i=1 To 100;
x(i)=y(i)+C*B;
End;
Здесь C*B инвариантно. Тогда после преобразований:
T001=C*B;
Do i=1 To 100;
x(i)=y(i)+T001;
End;
88
12. Замена в цикле операции деления операцией умножения; обычно умножение
выполняется намного быстрее деления; что для цикла дает ощутимый результат:
Do i=1 To 100;
x(i)=y(i)/C;
End;
Имеем:
T001=1/C;
Do i=1 To 100;
x(i)=y(i)*T001;
End;
13. Разбиение цикла; оно увеличивает скорость выполнения программы, хотя и
увеличивает ее объем за счет вынесения за цикл инвариантного условия. Например, в цикле:
Do i=1 To 100;
If A>0 Then B(i)=C(i) Else B(i)=C(i)*2;
End;
Условие А>0 инвариантно, тогда:
If A>0 Then
Do i=1 To 100;
B(i)=C(i);
End
Else
Do i=1 To 100;
B(i)=C(i)*2;
End;
14. Уменьшение числа инициализаций и завершений цикла; в этом случае сокращается
время инициализации и проверки индекса цикла. Пусть имеем:
Do i=1 To 200;
Do j=1 To 100;
Do k=1 To 50;
………
(1)
(2)
(3)
End;
End;
End;
Тело цикла выполняется 1000000 раз, а инициализация циклов (1),(2) и (3), соответственно 1,
200 и 20000 раз. Завершение цикла (3) выполняется 1000000 раз, цикла (2) – 20000, а цикла (1)
– 200 раз. Итого 20201 инициализация и 1020200 – завершений. После реорганизации циклов
получим:
Do k=1 To 50;
Do j=1 To 100;
Do i=1 To 200;
………
(1)
(2)
(3)
End;
End;
End;
Теперь выполняется 5051 инициализация и 1005050 завершений.
15. Перемещение вычислений; выполнение п.11 для неявных циклов. Пусть имеется:
L: i=i+1;
x(i)=y(i)+B*C/P;
p=i*Sin(z);
If i<100 Then Goto L;
Здесь вычисления B*C и Sin(z) инвариантны. Поэтому:
T001=B*C;
T002=Sin(z);
L: i=i+1;
x(i)=y(i)+T001/P;
p=i*T002;
If i<100 Then Goto L;
16. Чистка фрагментов как объединение вычислений, которые встречаются в разных
ветвях программы. Пусть имеется:
If A>0 Then B=A-E*F;
Else B=A+(E*F)/A;
Тогда:
89
T001=E*F;
If A>0 Then B=A-T001;
Else B=A+T001/A;
17. Преобразование многомерных массивов в одномерные; можно уменьшить число
циклов, которые необходимо организовать в исходной программе для обработки элементов
массивов. Пусть, например:
DECLARE A(20,30,40) FIXED;
………
Do i=1 To 20;
Do j=1 To 30;
Do k=1 To 40;
A(i,j,k)=0;
End;
End;
End;
Программа становиться более эффективной, если применить:
DECLARE A(20,30,40) FIXED,
B(24000) FIXED DEFINED A;
………
Do i=1 To 24000;
B(i)=0;
End;
Здесь используется наложение (DEFINED) B на A.
18. Разукрупнение логических выражений; используется тот факт, что логические
выражения продолжают вычисляться в то время, когда результат уже известен:
If A>B|C<D Then Goto L;
эффективнее
If A>B Then Goto L;
If C<D Then Goto L;
Оператор If (A>B)&(C<D) Then E=(A-B)/(C-D); выполняется медленнее, чем:
If A>B Then
If C<D Then E=(A-B)/(C-D);
19. Сокращение безусловных переходов; отслеживаются ступенчатые переходы типа:
Goto M1;
… ……
M1: Goto M2;
………
M2: … … …
сразу можно записать:
Goto M2;
… ……
M1: Goto M2;
………
M2: … … …
Обычно преобразование выполняется на основе графа выполнения программы (графа
управления) и графа информационных связей (зависимостей).
Методи побудови інтерпретаторів. Покрокові компілятори. Віртуальні
машини. Механізм макрогенерації в процесі компіляції
На основе интерпретаторов организуется непосредственное выполнение входной
программы. Из-за больших накладных расходов в большинстве случаев организуется перевод в
некоторую внутреннюю форму, т.е. в последовательность команд, которые затем выполняются
интерпретатором. Промежуточное представление в той или иной форме кодируется как дерево
разбора (например, ПОЛИЗ) с атрибутами вершин, множество которых обычно отличается от
подобного множества при чистой компиляции. Например, у переменной может появиться
атрибут "значение переменной", у выражения – "значение выражения".
Подходы к построению интерпретаторов различаются. Однако любой интерпретатор
последовательно выбирает “кусочки” промежуточного кода, распознает этот код как команду и
выполняет ее (интерпретирует) программно. Счетчик команд интерпретатора отслеживает
положение следующей выбираемой команды. Его значение изменяются скачком при
выполнении команд перехода.
90
Интерпретатор Smalltalk-80 выполняет байт-коды скомпилированного метода либо,
если метод базовый, то распознает код метода и ищет собственно подпрограммы для
реализации базового метода. Используется следующая структура данных.
Текущий объект, для
которого выполняется
метод
Стек (соответствует записи
активации)
self
Текущий
элемент
Текущая
верхушка
стека
Ссылка
к
предыдущей
записи
Указатель метода
Счетчик, определяющий текущий
выполняемый байт-код
Переменные
экземпляра
Компилированный
метод
Область параметров и
локальных переменных
метода
Резервная область для заполнения текущими,
вычисляемыми значениями (указателями).
Байт-коды
Ссылки на глобальные
объекты и константы
Например, байт-коды от 0 до 15 задают размещение в верхушке стека указателя на
объект, соответствующий переменной экземпляра с номером от 0 до 15.
После выполнения команды байт-кода увеличивается счетчик команд и выбирается
следующий для выполнения. Байт-коды 120-125 задают различные способы возврата
результата вычисления метода. Обычно верхнее слово текущего контекста переписывается в
верхушку предыдущей записи активации.
Интерпретатор ПОЛИЗ обрабатывает массив целых чисел Р, в котором создаются как
команды интерпретатора, так и операнды. Операнды представляют ссылки на элементы ТС.
Контекст хранится непосредственно в элементах ТС или в отдельном массиве, куда из
элемента ТС имеется ссылка. Переменные содержат значения относительного расположения в
блоке данных (записи активации) и содержат номер уровня вложенности. Всегда можно,
получив доступ к активному дисплею, вычислить, где располагаются значения переменной.
Шаговый компилятор генерирует код, разбитый на фрагменты, которые подобно
интерпретатору исполняются. Разрешается задавать точки останова выполнения
скомпилированного кода. В целом шаговый компилятор напоминает работу отладчика
(Debuger) для процедурных языков.
Виртуальные машины стали использоваться преимущественно для распределенных
приложений в связи с обеспечением мобильности программ для гетерогенных вычислительных
сред. Поскольку в сетевой среде необходимо поддерживать вычисления независимо от
компьютерной платформы, то код транслируется на некоторый усредненный промежуточный
язык, не учитывающий платформенную привязку, поскольку она привносится уже при
интерпретации этого промежуточного языка на конкретном компьютере.
Без виртуальных машин невозможно сейчас проводить обслуживание тонких клиентов в
on-line-приложениях Интернета. Ответы на запросы тонких клиентов генерируются в форме
HTML-страниц, которые создаются на Web-серверах посредством аплетов или специальных
сценариев. Всякий браузер – это виртуальная машина, интерпретирующая HTML-страницу.
Другой пример виртуальных машин – встраивание метафайла для выполнения
графических построений по библиотеке графических примитивов.
91
Download