- •4. Предикация, поток управления и поток инструкций
- •4.1. Краткий обзор
- •4.2. Предикация
- •4.2.1. Стоимость эффективности переходов
- •4.2.1.1. Ресурсы предсказания
- •4.2.1.2. Планирование инструкций
- •4.2.2. Предикация в архитектуре IntelItanium
- •4.2.3. Оптимизация эффективности программы использующей предикацию
- •4.2.3.1. Применение преобразования типа if
- •4.2.3.2. Предикация вне пути
- •4.2.3.3. Перемещение кода вверх.
- •4.2.3.4. Перемещение кода вниз
- •4.2.3.5. Сокращение засорения кэша.
- •4.2.4. Соображения о предикации
- •4.2.4.1. Несбалансированные пути выполнения.
- •4.2.4.2. Случай 1
- •4.2.4.3. Случай 2
- •4.2.4.4. Случай 3
- •4.2.4.5. Использование наложения ресурсов
- •4.2.4.6. Случай 1
- •4.2.5. Рекомендации по удалению ветвей
- •4.3. Оптимизация потока управления
- •4.3.1. Уменьшение критического пути при параллельном сравнении
- •4.3.2. Сокращения критического пути с помощью ветвлений.
- •4.3.3. Выбор множества значений для одной переменной или регистра с предикацией
- •4.3.3.1. Выбор одного из нескольких значений
- •4.3.3.2. Уменьшение использования регистров.
- •4.3.4. Улучшение выборки потока инструкций
- •4.3.4.1. Выравнивание потока инструкций.
- •4.4. Подсказки перехода и предвыборки
- •4.5. Итоги
4.2.3.4. Перемещение кода вниз
Также как и при перемещении кода вверх, перемещение кода вниз затруднено при присутствии сохранений. Следующий пример показывает, как код может быть передвинут мимо метки вниз, это преобразование часто опасно без предикации:
ld8 r56 = [r45];; // Такт 0: загрузка
st4 [r23] = r56;; // Такт 2: сохранение
label_A:
add ... // Такт 3
add ...
add ...
add ...;;
В вышеприведенном коде предполагаемое время ожидания между загрузкой и сохранением составляет два такта. Допустим, что инструкция загрузки не может быть передвинута вверх из-за других зависимостей, единственный способ перепланирования инструкций так, чтобы время выполнения совмещалось – это передвинуть сохранение вниз, мимо метки.
Следующий код демонстрирует общую идею использования предикатов для разрешения перемещения кода вниз. В коде, фактически сгенерированном компилятором, предикаты, явно вычисляемые в этом примере, уже могли бы быть доступны в предикатных регистрах, и не требовать дополнительных инструкций.
// Точка, которая "доминирует" над label_A
cmp.ne p1,p0 = r0,r0 // Инициализация p1 для false
// Другие инструкции
cmp.eq p1,p0 = r0, // Инициализация p1 для true
ld8 r56=[r45];; // Такт 0
label_A:
add ... // Такт 1
add ...
add ...
add ...;;
(p1) st4 [r23]=r56 // Такт 2
Здесь перемещение кода вниз сохраняет один такт. Имеются примеры более сложных ситуаций, включающих циклическое планирование, другие ограниченные сохранениями перемещения кода или вынесения кода за пределы циклов, но они не описываются здесь.
4.2.3.5. Сокращение засорения кэша.
Загрузки и сохранения с предикатами, которые являются ложными во время выполнения, обычно, вероятно, не трогают строки кэша для удаления, замены или вызова. Также не требуется ни каких дополнительных инструкций или кода восстановления, как это было необходимо при спекуляциях по данным или управлению. Поэтому, когда использование предикации приводит к такой же длине критического пути, что и спекуляция по данным или управлению, то почти всегда предпочтительнее использовать предикацию.
4.2.4. Соображения о предикации
Хотя предикация имеет рад выгодных эффектов, но имеется несколько случаев, где использование предикации должно рассматриваться тщательнее. Такие случаи обычно связаны с путями выполнения, которые имеют несбалансированное общее время выполнения либо со слишком интенсивным использованием специфического ресурса, типа тех, которые связаны с операциями памяти.
4.2.4.1. Несбалансированные пути выполнения.
Нижеприведенное простое условное выражение имеет не сбалансированный размер потоковой зависимости. Предположим, что не предикатное ассемблирование для этой последовательности занимает два такта для блока if, и примерно 18 тактов, если мы считаем, чтоsetfзанимает 8 тактов,getfзанимает 2 такта, аxmaзанимает 6 тактов:
if (r4) // 2 такта
r3 = r2 + r1;
else // 18 тактов
r3 = r2 * r1;
f (r3); // Целочисленное использование r3
Ниже показано преобразование типа ifдляItaniumкода. Количество тактов показано в зависимости от значенийp1иp2и описывает время выполнения:
// Использовано тактов, если p2есть:True False
cmp.ne p1,p2=r4,r0;; // 0 0
(p1) add r3=r2,r1 // 1 1
(p2) setf f1=r1 // 1 1
(p2) setf f2=r2;; // 1 1
(p2) xma.l f3=f1,f2,f0;; // 9 2
(p2) getf r3=f3;; // 15 3
(p2) use of r3 // 17 4
Этот код требует восемнадцати тактов, если p2есть истина, но пяти тактов, еслиp2есть ложь. При анализе таких случаев рассматриваются веса выполнения, вероятности промахов предсказания, и затраты на предикацию по каждому пути.
В трех сценариях, представленных ниже, предположим, что промах перехода стоит десять тактов. Нет кэша инструкций или штрафы, связанные с принятием перехода не рассматриваются.