Если какая-то команда хочет обратиться в оперативную память, то она конфликтует за доступ к шинам адреса и данных со считыванием очередной команды; это устранено в системах с раздельными кэшами для команд и для данных.
Если команда хочет использовать данные, которые еще не вычислены (например, использует в качестве аргумента регистр, чье значение изменяется предшествующей командой, а эта команда еще не записала результаты), то конвейер должен быть приостановлен до разрешения этого конфликта. Если команда изменяет содержимое памяти, в которой содержатся команды, уже поступившие в конвейер, то процессор имеет полное право проигнорировать это и продолжать выполнение содержащихся в конвейере команд.
Однако, пожалуй, наиболее серьёзным камнем преткновения являются условные ветвления, ведь процессор не может знать, будет переход выполнен или нет, до тех пор, пока команда не пройдет исполнительную ступень конвейера. Для ускорения определения направления условного перехода применяется так называемое предсказание ветвлений. Одни участки кода выполняются чаще чем другие (собственно, на этом и построена идея кэширования); то же самое относится к участкам, определяемым ветвлениями. Чаще всего ветвление служит либо для проверки исключительной ситуации, либо для организации цикла; в любом случае, зная, с какой частотой до этого совершался переход, можно оценить вероятность перехода в этот раз и направить конвейер по наиболее вероятному направлению. Процессор определяет наиболее вероятное направление перехода либо статически (по заранее заданным правилам), либо динамически — по собираемой им статистике выполнения этого ветвления за прошедшее время (так как часто переход выполняется более одного раза). А в некоторых моделях (чаще всего с программным распараллеливанием) и программисту оставлена возможность “намекнуть“, куда переход более вероятен! Декларируемая вероятность правильного определения переходов в современных процессорах — 90-95%. Предсказанная ветвь сразу направляется на конвейер для выполнения — это и есть упреждающее выполнение переходов – и если предсказание “сбылось“, то переход выполняется за один такт. Но если процессор ошибся, то приходится очищать весь длиннющий конвейер и заполнять его уже командами из “правильной“ ветви. Здесь и теряется драгоценное время. Ограниченность этого метода очевидна - если бы можно было гарантированно предсказать результат ветвления, то его можно было бы заменить безусловным переходом. Один из напрашивающихся путей решения проблемы – посылать вторую ветвь условного перехода на дополнительный конвейер параллельно с первой и после проверки условия ветвления дальше идти лишь в правильном направлении, отменив выполнение другой ветви.
В случае дополнительного конвейера, при обнаружении ветвления основной конвейер продолжает выполнение основного потока, как будто уверен, что перехода не произойдет, а дополнительный конвейер выполняет команды начиная с той, куда должен произойти переход (естественно, ни один не записывает результаты вычислений); по вычислении условия и выполнении оператора ветвления становится окончательно ясно, какой из конвейеров работал впустую. Ну, а если и в ветвях есть свои условные ветвления? Нелегкая задачка... Поэтому, применимость этого метода ограничивается возможностью появления в одном из этих конвейеров (а то и в обоих) новых операций ветвления. Можно, конечно, заиметь несколько конвейеров или вести очередь ветвлений - в любом случае сложность диспетчеризации конвейеров экспоненциально растет с глубиной конвейера.
Суперскалярная архитектура подразумевает одновременное выполнение нескольких команд в параллельно работающих исполнительных устройствах. Например, происходит параллельная работа арифметико-логических блоков, проверка условий ветвления для команд условного перехода. При этом исполнительное ядро работает с повышенной скоростью выполнения операций (Intel называет это RapidExecuteEngineр – “Устройство Быстрого Исполнения”). Проблемы появляются, когда, например, одна команда должна получить на вход результат выполнения другой.
Последовательность команд, составляющих программу, в большинстве случаев соответствует принципу фон Неймана - один поток команд, каждая из которых работает со своими данными. Однако нередко последовательность выполнения команд можно изменить, не оказав влияния на результат выполнения программы:
Читать дальше