- •3. Обращение к памяти
- •3.1. Краткий обзор
- •3.2. Не спекулятивные обращения к памяти
- •3.2.1. Сохранения в память
- •3.2.2. Загрузки из памяти
- •3.2.3. Подсказки предвыборки данных
- •3.3. Зависимости инструкций
- •Раздел 3.4. Описывает определенные в архитектуре Itanium, особенности обращений к памяти, увеличивающие число зависимостей, которые могут быть удалены транслятором.
- •3.3.1. Зависимости по управлению
- •3.3.1.1. Планирование инструкций и зависимости по управлению
- •3.3.2. Зависимости по данным
- •3.3.2.1. Основы зависимости по данным
- •3.3.2.2. Зависимость по данным в архитектуре IntelItanium
- •3.3.2.3. Планирование инструкций и зависимости по данным
- •3.4. Использование спекулятивности в архитектуре IntelItaniumдля преодоления зависимостей
- •3.4.1. Модель спекулятивности в архитектуре IntelItanium
- •3.4.2. Использование спекуляции по данным в архитектуре IntelItanium
- •3.4.2.1. Примеры предварительных загрузок
- •3.4.2.2. Пример кода восстановления
- •3.4.2.3. Краткий обзор терминологии
- •3.4.3. Использование спекуляции по управлению в архитектуре IntelItanium
- •3.4.3.1. Бит NaT
- •3.4.3.2. Пример спекуляции по управлению
- •3.4.3.3. Сливания, заливания и регистр unat
- •3.4.3.4. Краткий обзор терминологии
- •3.4.4. Комбинирование спекуляций по данным и управлению
- •3.5. Оптимизация обращений к памяти
- •3.5.1. Соображения о спекуляции
- •3.5.2. Взаимное влияние данных
- •3.5.3. Оптимизация размера кода
- •3.5.4. Использование постинкрементных загрузок и сохранений.
- •3.5.5. Оптимизация циклов.
- •3.5.6. Минимизация кода проверки
- •3.6. Итоги
3.5.4. Использование постинкрементных загрузок и сохранений.
Постинкрементные загрузки и сохранения могут улучшить эффективность путем объединения в одной инструкции двух операций. Хотя в тексте этого раздела упоминаются только постинкрементные загрузки, но основная информация также применима и к сохранениям.
Постинкрементные загрузки запускаются на исполнительных устройствах типа М и могут увеличивать свой адресный регистр либо с помощью непосредственного значения, либо с помощью содержимого общего регистра. Следующий псевдокод, который выполняет две загрузки:
ld8 r2=[r1]
add r1=1,r1 ;;
ld8 r3=[r1]
может быть переписан с использованием постинкрементной загрузки:
ld8 r2=[r1],1 ;;
ld8 r3=[r1]
Постинкрементные загрузки не могут предлагать прямые сохранения в вершину зависимого пути, но они важны, когда вычисляются адреса, которые нужны для последующих загрузок:
Постинкрементные загрузки избегают расширения размеров кода путем объединения двух инструкций в одну.
Сложение может быть запущено либо на устройстве типа I, либо – типаM. Если программа объединяет сложение и загрузку, то один из ресурсов устройствIилиMостается свободным, а иначе он был бы использован. Таким образом, производительность, зависящая от сложений и загрузок, может быть удвоена с помощью постинкрементных загрузок.
Недостатком постинкрементных загрузок является то, что они создают новые зависимости между постинкрементными загрузками и операциями, которые используют постинкрементные значения. В некоторых случаях компилятор может захотеть разделить постинкрементные загрузки на отдельные инструкции для того, чтобы улучшить общее планирование. Альтернативно, транслятор может дождаться окончания планирования инструкций, а затем специально выискивать места, где постинкрементные загрузки можно заменить, на единые инструкции загрузки и сложения.
3.5.5. Оптимизация циклов.
В циклическом коде, спекуляция может расширить использование классической оптимизации циклов, подобно перемещению инвариантного (неизменного) кода. Исследуем этот псевдокод:
while (cond) {
c = a + b; // Вероятно инвариант цикла
*ptr++ = c; // Может указывать на a или b
}
Переменные aиbвероятно являются инвариантами цикла (т.е. не меняются в цикле), однако компилятор должен предположить, что сохранения в*ptrбудут перезаписыватьaиb, если анализ не гарантирует, что этого никогда не случится. Использование предварительных загрузок и проверок позволяет удалить из цикла код, который вероятно будет инвариантным, даже когда указатель не может быть не двусмысленным.
ld4.a r1 = [&a]
ld4.a r2 = [&b]
add r3 = r1,r2 // Выносим вычисление из цикла
while (cond) {
chk.a.nc r1, recover1
L1: chk.a.nc r2, recover2
L2: *p++ = r3
}
А в конце модуля нужно добавить:
recover1: // Восстановление для неудачной загрузки a
ld4.a r1 = [&a]
add r3 = r1, r2
br.sptk L1 // Безусловный переход
recover2: // Восстановление для неудачной загрузки b
ld4.a r2 = [&b]
add r3 = r1, r2
br.sptk L2 // Безусловный переход
Использование в этом цикле спекуляции, скрывает время вычисления c, каждый раз, когда спекулятивный код будет успешен.
Так как проверки имеют две формы – с очисткой (clr) и без очистки (nc), то программист должен выбрать какую из них использовать. Этот пример показывает, что когда проверки перемещаются вне цикла, то не должна использоваться версия с очисткой. Это так, потому что версия с очисткой (clr) вызовет удаление соответствующего элементаALAT(а это привело бы к тому, что при следующей проверке регистр был бы ошибочным).