Конфликты исполнения инструкций на линейном конвейере команд. Суперконвейеризация
Несмотря на высокую потенциальную производительность рассмотренного линейного конвейера на практике в силу возникающих на конвейере конфликтных ситуаций достичь такой производительности не удается. Конфликтные ситуации на конвейере принято обозначать термином риск (hazard), а обусловлены они могут быть следующими причинами:
• попыткой нескольких команд одновременно обратиться к одному и тому же ресурсу ВМ, как правило, к одному и тому же регистру процессора (структурный риск);
• взаимосвязью команд по данным (риск по данным), тогда две или более инструкции проходящие по конвейеру предусматривают использование общих данных. При этом одна из инструкций может обратиться к таким данным еще до того как предшествующая ей инструкции зафиксировала результаты своего исполнения над ними;
• неоднозначностью при выборке следующей команды в случае прохождения по конвейеру команды перехода (риск по управлению).
Структурный риск (structural hazard) имеет место, когда несколько команд, находящихся на разный стадиях конвейера, пытаются одновременно использовать один и тот же ресурс, чаще всего регистры процессора или ячейка памяти.
Например, большинство современных процессоров имеют такую программную модель, которая предполагает наличие особого регистра общего назначения, называемого как правило аккумулятором или рабочим регистром, операции над содержимым которого выполняются наиболее быстро, а ряд инструкций требуют обязательного его использования для размещения операнда. В результате в программе встречаются фрагменты последовательностей инструкций идущих подряд, которые используют этот регистр. В результате на конвейер часто попадают две или более инструкции, находящиеся на различных стадиях исполнения и требующих использования одного и того же регистра. Структурного риска удается избежать путем расширения числа регистров в программной модели процессора или введением так называемых параллельных теневых регистров (shadow register). В первом случае в программной модели процессора предполагается наличие регистрового пула (registers pool) состоящего из многочисленных равнозначных регистров общего назначения, так что каждая команда может использовать отдельный рабочий регистр. Во-втором случае, для решения задачи неизменности программной модели и сохранения совместимости с ранее разработанным программным обеспечением дополнительные регистры дублирующие функциональность основных реализуются в составе исполнительного ядра процессора доступные для использования только на этапе прохождения команд на конвейере. В этом случае для устранения структурного риска на этапе генерации адресов операндов формируются указания на свободные теневые регистры, исключая совместное использование одного регистра.
В целом, влияние структурного риска на производительность конвейера по сравнению с другими видами рисков сравнительно невелико, а методы его устранения технически тривиальны.
Риск по данным (data hazard), в противоположность структурному риску – типичная и регулярно возникающая ситуация. Предположим, что две команды (i и j) на конвейере предусматривают обращение к одной и той же переменной x, причем команда i предшествует команде j . Наиболее ожидаемым риском в этом случае является, то что команда j прочитает значение x до того, как команда i успела записать новое значение x, то есть j ошибочно получит старое значение x вместо нового. Вычислительный процесс при этом будет нарушен.
Разрешение конфликтов связанных с данными возможно двумя способами. Первый способ основан на использовании компиляторов – инструментальных программных средств выполняющих преобразование программы, написанной на языке программирования высокого уровня в двоичный код инструкций процессора, генерирующих такую последовательность команд, в результате исполнения которых устраняется сама возможность конфликта. Между потенциально «конфликтными» командами размещаются некоторое количество нейтральных в этом плане инструкций. Если это невозможно, то между конфликтующими командами размещаются некоторое количество инструкций типа «нет операции». Естественно такой подход к кодогенерации оказывает отрицательно на эффективности программы.
Фактическое разрешение такого конфликта возлагается на аппаратные методы. Наиболее очевидным решением является остановка команды j на несколько тактов с тем, чтобы команда i успела записать новое значение x или по крайней мере миновать ступень, вызывающую конфликт. Соответственно на конвейере задерживаются команды следующие за командой j. Данную ситуацию называют «пузырьком» в конвейере. Таким образом, как программные, так и аппаратные методы при линейной организации конвейера не могут разрешить риск по данным без потери эффективности хода вычислительного процесса.
Несмотря на это влияние конфликтов по данным в меньшей степени определяет потерю эффективности конвейера. Наибольшие проблемы при создании эффективного конвейера обусловлены командами, изменяющими естественный порядок вычислений. Реальные программы практически никогда не бывают линейными. В них обязательно присутствуют команды управления, изменяющие последовательность вычислений: безусловный и условный переход, вызов подпрограмм и возврат из них. Под переходом понимается запланированное алгоритмом изменение последовательного характера выполнения программы. Как показывает анализ типичной программы на каждые 6-8 команд приходится одна команда перехода. Последствия этого таковы, что через каждые 6-8 команд конвейер нужно очищать и заполнять заново в соответствии с адресом перехода.
Ниже приведен фрагмент кода программы написанной на ассемблере – машинном языке, каждой команде которого соответствует машинная двоичная инструкция. В данном фрагменте присутствует команда условного перехода Наглядно прохождение по конвейеру данной последовательности инструкций показано рис.4.
je l ; команда условного перехода на метку l
mov AX, 10h
inc CX
int 2Fh
mov BX, 5
l: mov AX, 20h ; команда соответствующая адресу перехода

Рис. 4. Прохождение по конвейеру команд, в последовательности которых присутствует инструкция условного перехода
Результаты исполнения первой инструкции <jel>, которая может привести к передачи управления на метку l, станут известными в ходе ее обработки в ФБ5. К эитому моменту времени четыре инструкции следующие за командой перехода уже будут находится на конвейере в различных стадиях исполнения. Если в конечном итоге первая инструкция приведет к переходу, то промежуточные результаты обработки следующих за ней на конвейере инструкций уже не будут востребованы, поскольку должна будет выполнена инструкция по целевому адресу перехода, т.е. команда <mov AX, 20h>. В создавшейся ситуации ход вычислительного процесса должен быть приостановлен, промежуточные результаты инструкций аннулированы, конвейер очищен и загружена инструкция находящаяся по адресу перехода. Данные процесс получил название «продувки» конвейера (pipeline flush).
Поскольку проблема условных переходов является наиболее чувствительной для реализации эффективной конвейеризации вычислений и именно она приводит к наибольшим издержкам в работе конвейера, то основные усилия проектировщиков ВМ и микропроцессоров направлены на решение этой проблемы. Несмотря на то, что существует несколько методов решения проблемы условного перехода, которые позволяют устранить или частично сократить эти издержки, однако наиболее эффективным на сегодняшний день является метод предсказания переходов (branch prediction). Идея этого метода заключается в том, что еще до момента выполнения команды условного перехода или сразу после ее поступления на конвейер делается предположение о наиболее вероятном исходе такой команды. Последующие команды подаются на конвейер в соответствии с предсказанием. В соответствии с идеей этого методом одним из аспектов построения эффективных микропроцессоров является реализация механизма предсказания правильного адреса перехода.
В архитектуру процессоров 5-го поколения, например процессор в Pentium, был введен блок предсказания переходов. Процессор Pentium имеет буфер адресов переходов (branch target buffer), который хранит информацию о последних 256 переходов. В буфере представляет собой кэш-память небольшого объема организованной в виде ассоциативной таблицы, в каждой записи которой содержится соответствие целевому адресу перехода и адресу инструкции этот переход вызвавшей. Если некоторая команда управляет ветвлением, то в буфере запоминается эта команда, адрес перехода, а также предположение о том, какая ветвь программы будет выполнена следующей. Этот блок прогнозирует, какое решение будет принято программой. При этом он основывается на предположении, что ветвь, которая была пройдена однажды будет использоваться снова с большей степень вероятности, и загружает на конвейер команды в соответствии с адресом предполагаемого перехода. Вероятность правильного предсказания при этом составляет порядка 80%.
Таким образом, из рассмотренного выше становится очевидным, что эффективность конвейеризации вычислений зависит не только от того с какой частотой подаются на его вход команды, но и от того как эффективно разрешаются проблемы непротиворечивой их обработки. Очевидно, также что чем больше число ступеней на конвейере, тем сильнее усугубляется проблема конфликтов различного вида и сложнее становится его техническая реализация. Тем не менее современные процессоры развиваются не только за счет введения в свою микроархитектуру нескольких конвейеров (суперскалярность), то и за счет увеличения числа ступеней конвейера. Последний подход получил название суперковейеризация (superpipelining). Критерием причисления процессора к суперконвейерным служит число ступеней, которых должно быть не менее шести. Например, процессор Pentium III имеет 10 ступеней, процессор UltraSPARC III 14 ступеней, а процессор Pentium 4 уже 20 ступеней. Несмотря на существенное усложнение логики работы и взаимосвязей между стадиями конвейера разработчикам удается реализация эффективных суперконвейерных процессоров с использование новых методов организации вычислительного процесса и архитектурных подходов к построению вычислительного ядра процессора.
