Конфликты типа RAW (Read After Write - чтение после записи)
Команда с номером j пытается прочитать операнд прежде, чем команда с номером i запишет на это место свой результат. При этом команда с номером j может получить некорректное старое значение операнда.
Проиллюстрируем этот тип конфликта на примере выполнения следующих команд
i) ADD R1,R0; (R1=R1+R0) i+1=j) SUB R2,R1; (R2=R2-R1).Команда с номером i изменит состояние регистра R1 в такте 5. Но команда с номером i+1 должна прочитать значение операнда R1 в такте 4. Если не приняты специальные меры, то из регистра R1 будет прочитано значение, которое было в нем до выполнения команды с номером i.
Конфликты типа RAW обусловлены именно конвейерной организацией обработки команд. Они называются истинными взаимозависимостями.
Уменьшение влияния конфликта типа RAW обеспечивается методом, который называется пересылкой или продвижением данных, обходом, иногда закороткой.
В этом случае результаты, полученные на выходах исполнительных устройств, помимо входов приемника результата передаются также на входы всех исполнительных устройств ядра процессора.
Если устройство управления обнаруживает, что полученный какой-либо командой результат требуется одной из последующих команд в качестве операнда, то он сразу же, параллельно с записью в приемник результата, передается на вход исполнительного устройства для использования следующей командой.
Главной причиной двух других типов конфликтов по данным является возможность неупорядоченного выполнения команд в современных ядрах процессоров, то есть, выполнения команд не в том порядке, в котором они записаны в программе (ложные взаимозависимости).
Конфликты типа WAR (Write After Read - запись после чтения).Команда с номером j пытается записать результат в приемник, прежде чем он считается оттуда командой с номером i. При этом команда с номером i может получить некорректное новое значение операнда:
i) ADD R1,R0 (R1=R1+R0); i+1 = j) SUB R0,R2 (R0=R0-R2).Этот конфликт возникнет в случае, если команда с номером j вследствие неупорядоченного выполнения завершится раньше, чем команда с номером i прочитает старое содержимое регистра R0.
Конфликты типа WAW (Write After Write - запись после записи).Команда с номером j пытается записать результат в приемник, прежде чем в этот же приемник будет записан результат выполнения команды с номером i, то есть запись заканчивается в неверном порядке, оставляя в приемнике результата значение, записанное командой с номером i:
i) ADD R1,R0 (R1=R1+R0);. . .j) SUB R1,R2 ( R1=R1-R2).Устранение конфликтов по данным типов WAR и WAW достигается путем отказа от неупорядоченного выполнения команд, но чаще всего путем введения буфера восстановления последовательности команд.
Часть конфликтов по данным может быть снята специальной методикой планирования компилятора. В простейшем случае компилятор просто планирует распределение команд в базовом блоке.
Базовый блок представляет собой линейный участок последовательности команд программы с одним входом и одним выходом, в котором отсутствуют внутренние команды перехода. Поскольку в таком блоке каждая команда будет выполняться, если выполняется первая из них, - можно построить граф зависимостей этих команд и упорядочить их так, чтобы минимизировать приостановки конвейера. Эта техника называется планированием загрузки конвейера или планированием потока команд.
Например, для оператора А=B+С компилятор, скорее всего, сгенерирует следующую последовательность команд:
1. Rb = B.
2. Rc = C.
3. Ra = Rb + Rc.
4. A = Ra.
Очевидно, выполнение 3-ей команды должно быть приостановлено до тех пор, пока не станут доступными поступающие из подсистемы памяти операнды B и C.
Для данного простейшего примера компилятор никак не может улучшить ситуацию, однако в ряде более общих случаев он может реорганизовать последовательность команд так, чтобы избежать приостановок конвейера.
Пусть, например, имеется последовательность операторов:
А = B + С;D = E - F.В этом случае компилятор может сгенерировать следующую последовательность команд, выполнение которой не приведет к приостановке конвейера:
1. Rb = B.
2. Rc = C.
3. Re = E.
4. Ra = Rb + Rc.
5. Rf = F.
6. A = Ra.
7. Rd = Re - Rf.
8. D = Rd.
Заметим, что использование разных регистров для первого и второго компилируемого оператора было достаточно важным для реализации такого правильного планирования. В частности, если переменная Е была бы загружена в тот же самый регистр, что и B или C, такое планирование не было бы корректным. В общем случае планирование конвейера может потребовать увеличенного количества регистров.
Для простых конвейеров стратегия планирования на основе базовых блоков вполне удовлетворительна, однако когда конвейеризация становится более интенсивной и действительные задержки конвейера растут, требуются более сложные алгоритмы планирования.
Зачастую зависимость по данным не является необходимой - просто так уж повелось у программистов: чем меньше переменных (и регистров в программах на ассемблере) использует программа - тем лучше. В результате зачастую получается, что вся программа использует один-два регистра с зависимостью по данным чуть ли не в каждой паре команд.