Скачиваний:
58
Добавлен:
16.04.2013
Размер:
260.1 Кб
Скачать

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

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

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

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

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

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

Архитектура Itaniumулучшает эффективность обычных счетных циклов, с помощью специальной инструкции перехода счетного цикла (br.cloop) и прикладного регистраLC(LoopCount– счетчик цикла). Инструкция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, используются два порта памяти. Теперь, четыре итерации выполняются за пять тактов, по сравнению с двумя итерациями за четыре такта в предыдущей версии цикла.