Назначение и распределение регистров

Многие компиляторы имеют несколько регистров общего назначения. Машинные инструкции, которые используют регистры в качестве своих операндов, выполняются быстрее, чем аналогичные инструкции, требующие обращения к памяти.

Поэтому нужно стараться разместить в регистрах все промежуточные результаты и значения переменных, которые потом будут использованы в программе.

Рассмотрим в качестве примера последовательность четверок. ПеременнаяVALUE используется 1 раз в четверке (7) и 2 раза в четверке (9). Если было бы достаточно регистров, то можно было обратиться за этой величиной к памяти только один раз.

Аналогично четверка (16) запоминает значение i5 в переменной MEAN. Если сохранить i5 в регистре, то значение этой переменной может быть использвано, когда в четверке (18) потребуется значение переменной MEAN.

Использование регистров может сделать также ненужным использование многих большинства рабочих переменных.

Если рассматривать сгенерированный машинный код «нашей» программы, то можно увидеть, что при обработке шести из восьми промежуточных результатов ij достаточно использовать всего один регистр (AX).

На практике, конечно же, редко бывает доступно столько регсистров, сколько бы хотелось. Проблема, таким образом, сводится к выбору регистра, значение в котором должно быть заменено.

Один из возможных подходов состоит в просмотре программы вперед, чтобы определить, когда каждый из регистров будет повторно использоваться.

Переменная, которая не потребуется в течение наибольшего времени, определит искомый регистр.

Если регистр, в который нужно записать новую информацию, содержит значение некоторой переменной, уже находящейся в памяти, то старое значение может быть просто заменено. В противном случае её значение должно быть сохранено в рабочей переменной.

При распределении регистров компилятор должен также учитывать операторы передачи управления. Например, четверка (1) присваивает переменной SUM значение 0. Это значение можно было бы сохранить в каком-либо регистре для дальнейшего использования, например, в четверке (7), в которой требуется значение переменной SUM. Однако операция J в четвёрке (14) передает управление четвёрке (4). Если управление передается четверке (7) таким образом, то значение переменной SUM окажется незанесенным в регистр. Эту проблему часто решают с помощью разбиения на линейные участки.

Линейный участок – последовательность команд с одной точкой входа, с одной точкой выхода и без переходов внутри нее.

Каждая четверка, на которую может быть передано управление, или четверка, непосредственно следующая за четверкой передачи управления, начинает новый линейный участок.

Поскольку вызов процедуры может оказать непредсказуемое влияние на содержание регистров, операция CALL обычно также рассматривается как начало линейного участка.

Распределение и использование регистров внутри линейного участка может быть осуществлено описанным методом. Однако когда управление передается от одного линейного участка к другому, все находящиеся в регистрах значения сохраняются в рабочих переменных.

Покажем разбиение четвёрок на линейные участки для «нашей» программы.

Такая схема называется блок-схемой программы.

Стрелка означает, что управление передается непосредственно от последней четверки одного участка к первой четверке другого участка.

В рамках более сложного метода оптимизации блок-схема может стать объектом самостоятельного анализа с целью осуществления распределения регистров, сохраняющегося при переходе от одного участка к другому.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: