сист пр об ч 3 (1085772), страница 2
Текст из файла (страница 2)
Имя | Основание | Формат | Точность (в битах) | Тип памяти | Адрес |
С0SТ | ВINARУ | FIХЕD | 31 | SТАТIС | 0 |
RАТЕ | ВINАRУ | FIХЕD | 31 | SТАТIС | 4 |
SТАRТ | ВINАRУ | FIХЕD | 31 | SТАТIС | 8 |
FINISH | ВINАRУ | FIХЕD | 31 | SТАТIС | 12 |
Рис. 8.8. Таблица идентификаторов.
8.1.4. ЗАДАЧА № 3 —РАСПРЕДЕЛЕНИЕ ПАМЯТИ
В какое-то время мы должны отвести для нашей программы необходимую память. В нашем примере нужная для этого информация содержится в операторе DECLARE:
DECLARE ( COST , RATE , START , FINISH ) FIXED
BINARY ( 31 ) STATIC ;
Фаза интерпретации заполняет строки таблицы так, как изображено на рис. 8.8.
Программа распределения памяти просматривает таблицу идентификаторов и назначает память для каждого скаляра. В случае двоичных чисел с фиксированной точкой длиной в 32 бита, первый относительный адрес равен 0, второй — 4 и т. д. Абсолютный адрес остается неизвестным до загрузки. Фаза распределения памяти предполагает, что во время загрузки будет отводиться четыре 32-битовых слова (31 бит для числа и 1 для знака). Компилятор использует эту относительную адресацию в последующих фазах для правильных присваиваний.
Аналогично назначается временная память для промежуточных результатов, заполняющих матрицу (например, М1, М2, МЗ, М4, М5, М6, М7).
С0SТ | S | 3 |
RАТЕ | I | |
SТАRТ | G | |
FINISН | N |
Биты: О 31
Для языков, подобных ФОРТРАНу, где память распределяется во время компиляции, это довольно простая процедура. Нужно просмотреть таблицу, найти основание, формат, точность и выделить нужное количество памяти. Однако в языке ПЛ/1 только статическая память (SТАТIС) может распределяться подобным образом. Три других типа памяти (АUТОМАТ1С — автоматическая, СОNТRОLLЕD — управляемая и ВАSЕD— базированная) нельзя распределять описанным способом.
Информация об автоматических, управляемых или базированных переменных должна включаться в объектный модуль и быть доступной во время выполнения программы. Для таких операторов, как РRОСЕDURЕ, ВЕGIN и АLLОСАТЕ, компилятор генерирует объектный код, резервирующий необходимую память под такие переменные. Аналогично операторы END, RETURN и FRЕЕ должны генерировать объектный код для освобождения динамической памяти. Это приводит к тому, что подобные операторы должны иметь соответствующие строки в матрице, обеспечивающие генерацию нужного кода.
Доступ к такого рода памяти затруднен. Обычно компилятор даже не знает, сможет ли он назначить необходимую память, поскольку ее может просто не хватить. Обращение к таким данным осуществляется с использованием регистров, содержимое которых может изменяться во время выполнения программы с целью адресации динамически распределяемых данных.
8.1.5. ЗАДАЧА № 4 — ГЕНЕРАЦИЯ КОДА
После того как компилятор сформировал матрицу и таблицы, содержащие вспомогательную информацию, можно перейти к генерированию объектных кодов.
В одной из схем генерирования объектных кодов предусматривается таблица (таблица продуцирования кода), определяющая для каждого типа операции в матрице соответствующие объектные коды (рис, 8,9), Фаза генерации кода просматривает
матрицу и для каждой ее строки генерирует коды, определяемые в таблице, с использованием значений операндов, указанных в этой строке матрицы для дальнейшего уточнения кодов. Этот процесс показан на рис. 8.10, при этом используются определения кодов, приведенные на рис. 8.9. Один из путей реализации такой схемы состоит в том, что операции в матрице представляются как макрокоманды, операнды — как фактические параметры, а продуцирующая таблица — как макроопределения.
Стандартные кодовые определения для —, *, +, =
- L 1,& операнд 1
S 1,& операнд 2
ST 1, M&N
* L 1,& операнд 1
M 1,& операнд 2
ST 1, M&N
+ L 1,& операнд 1
A 1,& операнд 2
ST 1, M&N
= L 1,& операнд 1
ST 1,& операнд 2
Рис. 8.9. Кодовые определения — продукции для операций — , *, + , =.
При рассмотрении рис. 8.10 возникают следующие вопросы:
-
Насколько целесообразно генерировать коды непосредственно из матрицы? Строки 1 и 4 нашей матрицы идентичны, что приводит к формированию лишних кодов.
-
Используем ли мы машину, имеющуюся в нашем распоряжении, наилучшим образом? Строки 12 (SТ1, М4) и 13 (L 1, М4) просто лишние. Они никак не меняют результата вычислений. Кроме того, мы использовали только 2 из 16 общих регистров и только один тип команд (RХ) из пяти допустимых в Системе 360.
-
Можем ли мы генерировать непосредственно коды машинного языка? В примере используется язык ассемблера. Что будет, если один из операндов в матрице окажется меткой оператора, который еще не сгенерирован?
Первые два вопроса касаются оптимизации. Первый относится к целесообразности использования матрицы как промежуточной формы (машинно-независимой), а второй — к оптимальности машинного кода (машинно-зависимой форме). Компилятор сначала решает проблемы, поднятые в первом вопросе, поскольку, после того как будут исключены лишние коды, не надо будет тратить время на оптимизацию исключенных кодов.
Матрица Генерируемый код
L 1,START
1 - START FINISH S 1,FINISH
ST 1,M1
L 1,RATE
2 * RATE M1 M 0,M1
ST 1,M2
L 1,=F’2’
3 * 2 RATE M 0,RATE
ST 1,M3
L 1,START
4 - START FINISH S 1,FINISH
ST 1,M4
L 1,M4
5 - M4 100 S 1,=F’100’
ST 1,M5
L 1,M3
6 * M3 M5 M 0,M5
ST 1,M6
L 1,M2
7 + M2 M6 A 1,M6
ST 1,M7
L 1,M7
8 = COST M7 ST 1,COST
Рис. 8.10. Пример генерации кода.
8.1.5.1. Оптимизация (машинно-независимая)
Проблема, связанная с вопросом 1, является примером одного из возможных типов машинно-независимой оптимизации. Когда какое-либо подвыражение появляется в одном и том же операторе более одного раза (общее подвыражение), мы можем исключить все дублируемые строки матрицы, модифицируя все ссылки к исключенным строкам так, чтобы ссылки были только к оставшейся копии этого подвыражения (рис. 8.11). Результирующие коды, показанные в средней колонке на рис. 8.12, представляют собой улучшенный вариант кодов, приведенных на рис. 8.10.
Другими примерами машинно-независимой оптимизации являются;
Матрица с общим Матрица после исключения
подвыражением общего подвыражения
1 - START FINISH 1 - START FINISH
2 * RATE M1 2 * RATE M1
3 * 2 RATE 3 * 2 RATE
4 - START FINISH 4
5 - M4 100 5 - M1 100
6 * M3 M5 6 * M3 M5
7 + M2 M6 7 + M2 M6
8 = COST M7 8 = COST M7
Рис. 8.11. Исключение общих подвыражений.
1. Выполнение во время компиляции операций, у которых операндами являются константы.
Оптимизируемая матрица
1 - SТАRТ FINISН
2 * RАТЕ М1
3 * 2 RАТЕ
4
5 - M1 100
6 * M3 M5
7 + М2 M6
8 = М7 COST
Первая версия
L 1,SТАRТ
S 1,FINISН
SТ 1,М1
L 1,RАТЕ
М 0,М1
SТ 1,М2
L 1,=F’2’
M 0,RATE
ST 1,M3
L 1,M1
S 1,=F’100’
SТ 1,М5
L 1,М3
M 1,M5
SТ 1,М5
L 1,М2
A 1,М6
ST 1,М7
L 1,М7
SТ 1,СОЗТ
-
Байтов
Улучшенный код
L 1,SТАRТ
S 1,FINISН М1 → R1
L 3,RАТЕ
MR 2,1 M2→ R3
L 4,=F’2’
M 4,RATE M3→ R4
S 1,=F’100’ M5→ R1
MR 4,1 M6→ R4
AR 2,4 M7→ R2
ST 2,COST
36 байтов
L
-
Вынесение из цикла вычислений, содержащих неизменяемые операнды.
-
Использование свойств булевых выражений для минимизации их вычислений.
Все это более детально рассматривается во второй части данной главы. Сейчас важно понять, что машинно-независимая оптимизация матрицы возможна и что логически она должна предшествовать генерации кода. Такой процесс называется фазой оптимизации и он логически отделен от генерации кода.
8.1.5.2. Оптимизация (машинно-зависимая)
Проблемы, связанные с вопросом 2, могут быть хорошо проиллюстрированы сравнением двух возможных версий кода, генерируемого из приведенного выше примера оптимизированной матрицы.
На рис. 8.12 изображена матрица, из которой ранее было исключено общее подвыражение (М4). Для каждой строки матрицы показан код, сгенерированный с использованием операторов, определенных на рис. 8.9. В третьей колонке приведен улучшенный код, в том смысле, что он занимает меньшую память и работает быстрее благодаря более целесообразной смеси команд. Как же нам удалось получить эту более эффективную версию?
-
Мы лучше использовали временную память, заняв столько из 16 общих регистров, сколько смогли, и не запоминали промежуточные результаты, пока это не было необходимо. Это уменьшило числа команд загрузки и записи в память с 14 до 5.
-
Когда это возможно, мы использовали более короткие и быстрые команды (МR вместо М).
В этом примере машинно-зависимой оптимизации были уменьшены в два раза как размер памяти, необходимой для программы, так и время выполнения объектной программы. Машинно-зависимая оптимизация обычно проводится в процессе генерации кодов. В действительности мы можем расширить предыдущую схему генерации кодов, используя макропроцессор с псевдокомандами условной компиляции. Таким путем можно изменять генерируемые команды в соответствии с доступностью и содержимым временной памяти. Такая схема включала бы машинно-зависимую оптимизацию в фазу генерации кодов.