Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MProc / M9 / Оптимизация часть 5..doc
Скачиваний:
41
Добавлен:
16.04.2013
Размер:
260 Кб
Скачать

5. Программная конвейерная обработка и поддержка циклов

5.1. Краткий обзор

Архитектура Itanium обеспечивает обширную поддержку для программной конвейерной обработки циклов, включая ротацию регистров, специальные циклические переходы и прикладные регистры. Вместе с предикацией и поддержкой спекуляций, эти особенности помогают уменьшить объем кода, длину пути и промахи предсказаний перехода для циклов, которые могут быть программно конвейеризированы.

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

5.2. Терминология циклов и основная поддержка циклов

Циклы могут быть разделены на две категории: счетные и типа while. В счетных циклах, условие цикла основано на значении счетчика цикла и индекс выхода (trip) может быть вычислен до начала цикла. В while-циклах, условием цикла является более общее вычисление (а не просто счетчик), а индекс выхода не известен. Оба типа циклов прямо поддерживаются архитектурой.

Архитектура Itanium улучшает эффективность обычных счетных циклов, с помощью специальной инструкции перехода счетного цикла (br.cloop) и прикладного регистра LC (Loop Count – счетчик цикла). Инструкция br.cloop не имеет условия перехода. Вместо этого, решение о переходе основывается на значении регистра LC. Если LC больше нуля, то он декрементируется, а переход br.cloop принимается.

5.3. Оптимизация циклов

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

L1: ld4 r4 = [r5],4;; // Такт 0 Загрузка с постинкрементом 4

add r7 = r4,r9;; // Такт 2

st4 [r6] = r7,4 // Такт 3 Сохранение с постинкрементом 4

br.cloop L1;; // Такт 3

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

Этот раздел описывает два общих метода наложения циклических итераций, оба являются результатом расширения кода традиционных архитектур. Проблема расширения кода обращена к особенностям поддержки циклов в архитектуре Itanium, которые исследуются в этой главе ниже. Вышеприведенный цикл будет использоваться в нескольких следующих разделах, как пример запуска (и будет упоминаться ниже под именем исходный пример или цикл).

5.3.1. Развертывание цикла

Развертывание цикла – это методика, которая стремится увеличить доступный параллелизм уровня инструкций, делая и планируя множество копий тела цикла вместе. Регистрам в каждой копии тела цикла даются разные имена, для того, чтобы избежать ненужных зависимостей по данным типа WAW и WAR. Нижеприведенный цикл показывает код из нашего исходного примера, после двойного развертывания (всего две копии первоначального тела цикла) и планирования инструкций, в предположении, что есть два порта памяти и время ожидания для загрузок составляет два такта. Для простоты предположим, что индекс выхода из цикла – это константа N, которая кратна двум, поэтому не требуется выхода после первой копии тела цикла:

L1: ld4 r4 = [r5],4;; // Такт 0

ld4 r14 = [r5],4;; // Такт 1

add r7 = r4,r9;; // Такт 2

add r17 = r14,r9 // Такт 3

st4 [r6] = r7,4;; // Такт 3

st4 [r6] = r17,4 // Такт 4

br.cloop L1;; // Такт 4

Этот код не формирует максимально возможного количества ILP. Две загрузки стоят последовательно, поскольку они обе используют и изменяют r5. Аналогично, два сохранения используют и модифицируют r6. Переменная, которая инкрементируется или декрементируется при каждой итерации на одну и ту же величину, называется переменной индукции. Одна переменная индукции r5 (и аналогично r6), может быть расширена в два регистра, как это показано ниже:

add r15 = 4,r5

add r16 = 4,r6;;

L1: ld4 r4 = [r5],8 // Такт 0

ld4 r14 = [r15],8;; // Такт 0

add r7 = r4,r9 // Такт 2

add r17 = r14,r9;; // Такт 2

st4 [r6] r7,8 // Такт 3

st4 [r16] = r17,8 // Такт 3

br.cloop L1;; // Такт 3

По сравнению с исходным циклом, используется вдвое больше функциональных устройств и размер кода вдвое больше. Однако в такте 1 не запускается никаких инструкций, а функциональные устройства используются в остальных тактах. Использование может быть увеличено за счет развертывания на большее количество времени, но при этом еще больше увеличится код. Ниже приведен цикл развернутый четыре раза (в предположении, что индекс выхода кратен четырем):

add r15 = 4,r5

add r25 = 8,r5

add r35 = 12,r5

add r16 = 4,r6

add r26 = 8,r6

add r36 = 12,r6;;

L1: ld4 r4 = [r5],16 // Такт 0

ld4 r14 = [r15],16;; // Такт 0

ld4 r24 = [r25],16 // Такт 1

ld4 r34 = [r35],16;; // Такт 1

add r7 = r4,r9 // Такт 2

add r17 = r14,r9;; // Такт 2

st4 [r6] = r7,16 // Такт 3

st4 [r16] = r17,16 // Такт 3

add r27 = r24,r9 // Такт 3

add r37 = r34,r9;; // Такт 3

st4 [r26] = r27,16 // Такт 4

st4 [r36] = r37,16 // Такт 4

br.cloop L1;; // Такт 4

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