Архитектура ЭВМ

Конвейеризация

Разбить на страницы
Показывать лекцию целиком

Определение конвейера; конфликты при конвейерном исполнении машинного кода; особенности обработки конфликтов в CICS- и RISC-архитектурах; пример генерации машинного кода для CISC-процессора ARM и RISC-процессора MIPS для демонстрации приёмов обработки потенциальных конфликтов конвейеризации на уровне компиляции; параллельное исполнение команд на примере процессоров Intel x86

ОПРЕДЕЛЕНИЕ КОНВЕЙЕРА

Команды процессора часто задействуют значительное количество блоков процессора и исполняются за несколько тактов. Например, рассмотрим команду сложения числа из регистра с числом из памяти dd eax, DWORD PTR [ebp-0x8] в процессоре Intel 80386. Эта команда выполняется следующим образом:

  • в начале выполняется чтение числа из регистра eax в арифметико-логическое устройство (АЛУ);
  • значение адресного регистра ebp и константа -0x8 подаются на вход адресному сумматору (заметим, что здесь речь не о сумматоре, входящем в состав АЛУ, а об отдельном адресном сумматоре, предназначенном для арифметических действий над адресами; различных специализированных сумматоров в процессоре может быть достаточно много);
  • вычисляется адрес в памяти для второго операнда;
  • выполняется чтение по этому адресу числа из памяти во временный регистр, соединённый с АЛУ;
  • выполняется сложение двух чисел;
  • результат записывается в регистр eax.
  • Таким образом эта команда выполняется за 6 тактов (следует отметить, что этот пример существенно упрощён для наглядности). При этом, если не использовать дополнительных оптимизаций, то на каждом такте задействуется лишь часть блоков процессора, а остальные простаивают. Например, при сложении двух значений, которые находятся в регистрах процессора, блок работы с памятью бездействует.

    Конвейер - это механизм, предназначенный для распараллеливания выполнения команд программы между блоками процессора. Он позволяет загрузить блоки процессора при выполнении команд оптимально, без простоев.

    Приведём пример конвейера. Рассмотрим упрощённый процессор, у которого каждая команда исполняется в три этапа, точнее, за три такта:

  • чтение аргументов команды из памяти или регистра (ЧТН),
  • исполнение команды (ИСП),
  • запись результата в память или регистр (ЗАП).
  • На рис.10.1 изображен процесс выполнения этим процессором последовательности команд Команда 1, Команда 2, Команда 3, Команда 4, Команда 5 с использованием конвейера. Рассмотрим четвёртый такт конвейера, который выделен на рисунке. Команда 1 уже полностью выполнена, Команда 2 находится на этапе записи данных (ЗАП), Команда 3 - на этапе исполнения (ИСП), Команда 4 - на этапе чтения данных (ЧТН), а к выполнению Команды 5 процессор ещё не приступал. Из-за того, что на каждом такте процессор выполняет с помощью конвейера сразу несколько частей (этапов) команд, все пять команд выполняются за 7 тактов, а не за 15, как было бы при последовательном исполнении этой же цепочки команд. Это оказывается возможным в виду того, что процессор параллельно выполняет различные этапы команд.

    КОНФЛИКТЫ ПРИ КОНВЕЙЕРНОМ ИСПОЛНЕНИИ МАШИННЫХ КОМАНД

    Результаты выполнения команд используются следующими командами программы. Посмотрим, как это простое соображение согласуется с конвейером.

    Приведённый выше пример показывает, что Команда 3 не сможет использовать результаты Команды 2, поскольку свои входные данные она читает раньше, чем Команда 2 записывает свои результаты. В том случае, когда Команде 3 действительно нужны результаты Команды 2, Команда 2 и Команда 3 называются зависимыми.

    Ситуации, возникающие при конвейеризованном исполнении команд, которые препятствуют корректному выполнению очередной команды, называются конфликтами.

    Конфликты бывают следующих видов.

  • Конфликт по данным между зависимыми машинными командами заключается в том, что на конвейере одновременно находятся на разных стадиях выполнения команды, которые могут быть корректно исполнены лишь строго последовательно.
  • Конфликт по ресурсам возникает в ситуации, когда двум командам на конвейере одновременно нужен доступ к какому-либо блоку процессора, с которым в один момент времени может работать только одна команда.
  • Конфликт по управлению заключается в том, что следующая команда на конвейере является условным переходом, но условие для него ещё не вычислено предыдущей командой и не понятно, какую ветку условного оператора следует загружать на конвейер.
  • Поскольку конфликты по данным являются распространённой на практике проблемой, приведём ряд примеров таких конфликтов:

  • следующая команда на конвейере должна читать данные на выходе предыдущей, но предыдущая ещё не закончила их обработку: при этом нарушается зависимость "чтение после записи";
  • следующая команда записывает данные, но они используются (читаются) предыдущей командой, то есть при этом нарушается зависимость "запись после чтения";
  • следующая команда записывает (в регистры процессора или в оперативную память) свои результаты раньше предыдущей, из-за чего предыдущая потом может перезаписать эти результаты: при этом нарушается зависимость "запись после записи".
  • Сделаем замечание относительно третьего вида конфликтов (по управлению) отметим. Такие конфликты дополнительно осложняются тем обстоятельством, что заранее не ясно, в какое место программы произойдёт условный переход, и поэтому не понятно, из какой его ветки загружать на конвейер следующие команды. В связи с этим во многих процессорах есть система предсказания условных переходов, которая собирает статистику по переходам и выбирает наиболее вероятный вариант. В случае ошибки предсказания конвейер очищается от частично обработанных команд, что вызывает задержки.

    Для того чтобы программа могла работать корректно с применением конвейера, конфликтные ситуации требуется обрабатывать, то есть обнаруживать и разрешать. Обнаружение конфликтов оказывается непростой задачей, которую мы не будем здесь рассматривать. Разрешение конфликтов может выполняться различными способами. Самым простым способом является "торможение" конвейера (pipeline stall) с целью задержки выполнения следующей команды до момента исчерпания конфликта. Но, во-первых, конфликтную ситуацию требуется обнаружить, что не просто, во-вторых, задержки уменьшают преимущества конвейера.

    Выделим следующие подходы к обработке конфликтов.

  • Статическое переупорядочивание машинных команд при компиляции программ с языков высокого уровня в машинный код.
  • "Разнесение" конфликтующих команд при компиляции на безопасное расстояние друг от друга с помощью вставки необходимого количества специальной команды NOP (No Operation). Команда NOP ничего не делает, но замедляет работу программы на один такт, в некоторых процессорах - и на большее число тактов.
  • Динамическая обработка конфликтов во время исполнения программы - идентификация и разрешение конфликтов выполняется в момент выполнения программы. При этом процессор задерживает выполнение зависимых команд (как, например, Intel 80486), а также самостоятельно переупорядочивает команды, чтобы исключить конфликты с минимизацией потери времени (так действуют процессор Intel Pentium и последующие процессоры семейств Intel x86).
  • ОСОБЕННОСТИ ОБРАБОТКИ КОНФЛИКТОВ В CISC- И RISC-АРХИТЕКТУРАХ

    Рассмотрев общие подходы к обработке конфликтов на конвейерах, остановимся теперь на особенностях этой процедуры в RISC- и CISC-процессорах. Свойства машинного языка оказывают определяющее влияние на то, каким образом можно организовать конвейерное исполнение машинного кода. Как следствие, различия между CISC- и RISC-процессорами приводят к использованию различных подходов к обработке конфликтов.

    CISC-процессоры делают акцент на динамической обработке конфликтов, поскольку в CISC-процессорах команды имеют различную сложность и время работы, задействуют разные блоки процессора и обращаются к операндам различного вида. Кроме того, в некоторых семействах динамическая обработка конфликтов является единственным возможным решением, поскольку механизм конвейеризации в них появился не сразу, и требовалась совместимость с предыдущими версиями процессоров, то есть механизм конвейеризации не является глубоко интегрированным в процессор.

    Рассмотрим пример. Первым конвейеризованным процессором семейства Intel x86 был процессор Intel 80486. Для того, чтобы у него была возможность корректно и по возможности быстро исполнять машинный код, предназначенный для предыдущих версий не конвейеризованных процессоров, ничего не оставалось, кроме как "научиться" разрешать конфликты динамически. Ни на какие "подсказки" компилятора он при этом рассчитывать не мог, ведь существующий машинный код для предыдущих процессоров семейства Intel x86 про конвейеризацию ничего не знал.

    Реализация динамической обработки конфликтов в CISC-процессорах требует значительного усложнения конвейера. Для сравнения можно представить себе, как усложнился бы конвейер на машиностроительном заводе, если бы на нём приходилось одновременно собирать бульдозеры, комбайны и танки, вдобавок разных моделей. Отметим, что иногда компиляторы и низкоуровневые программисты всё же помогают CISC-процессорам, создавая максимально "безконфликтный" машинный код. Это положительно сказывается на скорости работы программ на старых процессорах, которые, как, например, Intel 80486, умеют при обнаружении конфликтов лишь задерживать выполнение команд, но не переупорядочивать их.

    Машинный код RISC-процессоров, с точки зрения способа хранения команд в оперативной памяти, оказывается проще, чем у CISC-процессоров. Команды RISC-процессоров выполняются за одинаковое число тактов, а более простой машинный язык упрощает обнаружение и обработку конфликтов. Некоторые современные RISC-процессоры (например, процессоры семейства ARM), также как и CISC-процессоры, обнаруживают конфликты и могут переупорядочивать исполнение команд, минимизируя таким образом потерю производительности из-за конфликтов. Другие же RISC-процессоры вовсе не обрабатывают конфликты, например, процессоры семейства MIPS (название семейства этих процессоров расшифровывается как Microprocessor without Interlocked Pipelined Stages, то есть микропроцессор без блокировок на конвейере). Такие процессоры требуют, чтобы подготовленный для них машинный код не содержал потенциальных конфликтов при конвейеризации. Эту "бесконфликтность" машинного кода обеспечивает компилятор.

    В таблице 10.1 представлен пример, показывающий, как может отличаться машинный код одной и той же программы для и CISC- и RISC-процессоров. В рамках этого примера представлен фрагмент программы на языке C, а также машинный код, порождённый для него под процессоры семейств ARM (CISC) и MIPS (RISC). В случае с MIPS-процессором компилятор в некоторых местах вынужден генерировать инструкции NOP для предотвращения конфликтов при конвейеризации, а процессор ARM справляется с конфликтами самостоятельно.

    Результаты трансляции для ARM и MIPS: обработка конфликтов процессором и компилятором
    C-программа Трансляция для ARM Трансляция для MIPS
    Результат Комментарий Результат Комментарий
    int a,b; ldr r3, [fp, #-8]; ldr r2, [fp, #-12] Загрузка переменных: а - в регистр r3, b - в регистр r2. lw $3,8($fp); lw $2,12($fp) Загрузка переменных: а - в регистр $3, b - в регистр $2.
    if (a>b) Nop Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде
    cmp r3, r2 Сравнение регистров r3 и r2 slt $2,$2,$3 Если $2< $3, загрузить 1 в регистр $2, иначе загрузить 0 в регистр $2
    ble .L2 Условный переход (если результат последнего сравнения был "\le ") на следующую за оператором if команду beq $2,$0,$L2 Условный переход (если $2 = $0) на следующую за оператором if команду. Спец. регистр $0 всегда содержит значение 0
    Nop Конфликт по управлению: эта команда всегда попадает на конвейер до того, как становится известно, выполнит ли предыдущая команда переход
    \{a = b;\} ldr r2, [fp, #-12] Загрузка переменной b в регистр r2 lw $2,12($fp) Загрузка переменной b в регистр 2
    nop Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде
    str r2, [fp, #-8] Выгрузка регистра r2 в переменную a sw $2,8($fp) Выгрузка регистра $2 в переменную a
    b = 0; L2: Метка .L2 - адрес следующей за оператором if команды $L2: Метка $L2 - адрес следующей за оператором if команды
    mov r2, #0; str r2, [fp, #-12] Загрузка 0 в регистр r2, а следом выгрузка из регистра r2 в переменную b sw $0,12($fp) Выгрузка из спец. регистра $0 (всегда содержит 0) в переменную b

    ПАРАЛЛЕЛЬНОЕ ИСПОЛНЕНИЕ КОМАНД

    Как уже многократно указывалось, повышение производительности современных процессоров осуществляется в значительной степени за счёт распараллеливания. Одним из популярных способов распараллеливания является конвейеризация. Но хотя конвейеризация и позволяет значительно ускорить исполнение программ, максимального эффекта она достигает лишь в идеальных ситуациях, когда конфликты не возникают, или когда удаётся переупорядочить команды таким образом, чтобы эти конфликты исчерпать. Для дальнейшего повышения производительности применяются (иногда в сочетании с конвейеризацией) более сложные решения.

    В качестве первой техники параллельного исполнения команд процессором рассмотрим суперскалярность - возможность одновременно выполнять несколько машинных команд за счёт наличия в процессоре нескольких однотипных функциональных блоков (арифметико-логических устройств, математических сопроцессоров и т.д.) В семействе Intel x86 первым процессором, где была реализована суперскалярность, был процессор Intel Pentium (1993 год). Этот процессор содержал два арифметико-логических устройства, которые позволяли исполнять одновременно две соседние команды, если они не зависели друг от друга. При этом независимые команды одновременно обрабатывались двумя разными конвейерами. Для этого компиляторы стремились генерировать машинный код, соседние команды которого не зависели бы друг от друга.

    Более продвинутой техникой параллельного исполнения машинных команд является внеочередное исполнение (Out of Order Execution). Эта техника позволяет исполнять команды программы не в порядке следования, а в порядке готовности к выполнению. При использовании внеочередного исполнения некоторое множество команд программы исполняется тогда, когда для всех этих команд готовы нужные им входные данные. Первой ЭВМ, в которой был реализован механизм внеочередного исполнения команд, был суперкомпьютер CDC 6600 компании Cray Research, созданный в 1963 году.

    Приведём следующий пример. Пусть в программе подряд идут следующие команды:

     (1) a = b / c (2) d = b + a (3) x = y * z 

    Деление двух чисел (команда (1)) является сложной командой, которая выполняется существенно дольше, чем, например, сложение. Вспомните алгоритм деления "в столбик": в нём последовательно выполняется много операций и проверок. Процессор выполняет деление практически так же, но только в двоичной системе. В то же время, от результатов команды (1) зависит выполнение команды (2), а выполнение команды (3) не зависит. Поэтому можно приступить к выполнению команд (1) и (3) одновременно, а команду (2) начать выполнять после вычисления значения a.

    Рассмотрим общий сценарий внеочередного исполнения команд в современных процессорах на примере распространённого известного процессора Intel Core i7. После выборки процессором очередной команды для исполнения эта команда делится на микрооперации - простые действия, такие как "сложить два числа", "записать значение в регистр", "выдать адрес ячейки на шину адреса" и подобные им. Микрооперации помещаются в специальный буфер переупорядочивания (Reorder Buffer), в котором они упорядочиваются так, чтобы, с одной стороны, не нарушить корректность программы, а с другой стороны оптимально загрузить блоки процессора. Причём в буфере находятся и переупорядочиваются одновременно микрооперации нескольких идущих подряд команд. Чтобы получить больше свободы для переупорядочивания команд, процессор выполняет переименование регистров: для разных микроопераций, работающих с одними и теми же регистрами, назначаются новые регистры. Получившиеся микрооперации выполняются шестью блоками процессора - тремя АЛУ и тремя блоками доступа к памяти. При обнаружении условного перехода одновременно начинают обрабатываться обе ветви then и else, то есть используется спекулятивное исполнение программы. Но после фактического вычисления условия фиксируются только результаты команд из выбранной в переходе ветки.

    Компания Intel реализовала внеочередное исполнение команд задолго до запуска в производство современных многоядерных процессоров, в рамках процессоров Intel Pentium Pro в начале 2000-х годов. Было замечено, что блоки процессора выполняли микрооперации эффективно, но часто простаивали, ожидая результатов выборки машинных команд из оперативной памяти. Для того, чтобы оптимально загрузить эти блоки, была создана технология HyperThreading. Она выбирала машинные команды из оперативной памяти в двух параллельных потоках. Одноядерный процессор с применением технологии HyperThreading, фактически, начинает работать, как двухъядерный. Технология HyperThreading была запатентована сотрудником компании Sun Microsystems в 1994 году, но впервые была реализована в лишь 2002 году (Intel).

    Вопросы

  • Дайте определение конвейера.
  • Дайте определение конфликта на конвейере.
  • Перечислите виды конфликтов конвейера.
  • Что такое зависимые команды?
  • Что такое конфликт по ресурсам?
  • Что такое конфликт по управлению?
  • Перечислите способы преодоления конфликтов.
  • Что такое статическое переупорядочивание команд при решении конфликтов?
  • Расскажите про использование команды NOP для разрешения конфликтов.
  • Что такое динамическое разрешение конфликтов?
  • Какой подход используется при разрешении конфликтов на конвейерах CICS-процессоров и почему?
  • Какой подход используется при разрешении конфликтов на конвейерах RICS-процессоров и почему?
  • Зачем нужна система предсказания условных переходов?
  • Как CISC-процессоры разрешают конфликты конвейера?
  • Каковы особенности разрешения конфликтов в RISC-процессорах?
  • Почему конвейеризация RISC-процессоров проще, чем CISC?
  • Что такое суперскалярность?
  • Что такое внеочередное исполнение машинных команд?
  • Расскажите про технологию HyperThreading.
  • Литература

  • Гуров В.В. Архитектура микропроцессоров / Интернет-университет информационных технологий, Интернет-Университет Информационных Технологий (ИНТУИТ), Бином. 2010. 272 с.
  • Хорошевский В.Г. Архитектура вычислительных систем.: Учеб. пособие. 2-e изд., перераб. и доп. M.: Изд-во МГТУ им. H.Э. Баумана, 2008. 520 c.
  • Таненбаум Э., Остин Т. Архитектура компьютера. 6-е изд. СПб.: Питер, 2013. 816 с.
  • Patterson D. A., Ditzel D. R. The case for the reduced instruction set computer //ACM SIGARCH Computer Architecture News. 1980, vol. 8, No 6, P. 25-33.
  • Страницы:

    Определение конвейера; конфликты при конвейерном исполнении машинного кода; особенности обработки конфликтов в CICS- и RISC-архитектурах; пример генерации машинного кода для CISC-процессора ARM и RISC-процессора MIPS для демонстрации приёмов обработки потенциальных конфликтов конвейеризации на уровне компиляции; параллельное исполнение команд на примере процессоров Intel x86

    ОПРЕДЕЛЕНИЕ КОНВЕЙЕРА

    Команды процессора часто задействуют значительное количество блоков процессора и исполняются за несколько тактов. Например, рассмотрим команду сложения числа из регистра с числом из памяти dd eax, DWORD PTR [ebp-0x8] в процессоре Intel 80386. Эта команда выполняется следующим образом:

  • в начале выполняется чтение числа из регистра eax в арифметико-логическое устройство (АЛУ);
  • значение адресного регистра ebp и константа -0x8 подаются на вход адресному сумматору (заметим, что здесь речь не о сумматоре, входящем в состав АЛУ, а об отдельном адресном сумматоре, предназначенном для арифметических действий над адресами; различных специализированных сумматоров в процессоре может быть достаточно много);
  • вычисляется адрес в памяти для второго операнда;
  • выполняется чтение по этому адресу числа из памяти во временный регистр, соединённый с АЛУ;
  • выполняется сложение двух чисел;
  • результат записывается в регистр eax.
  • Таким образом эта команда выполняется за 6 тактов (следует отметить, что этот пример существенно упрощён для наглядности). При этом, если не использовать дополнительных оптимизаций, то на каждом такте задействуется лишь часть блоков процессора, а остальные простаивают. Например, при сложении двух значений, которые находятся в регистрах процессора, блок работы с памятью бездействует.

    Конвейер - это механизм, предназначенный для распараллеливания выполнения команд программы между блоками процессора. Он позволяет загрузить блоки процессора при выполнении команд оптимально, без простоев.

    Приведём пример конвейера. Рассмотрим упрощённый процессор, у которого каждая команда исполняется в три этапа, точнее, за три такта:

  • чтение аргументов команды из памяти или регистра (ЧТН),
  • исполнение команды (ИСП),
  • запись результата в память или регистр (ЗАП).
  • На рис.10.1 изображен процесс выполнения этим процессором последовательности команд Команда 1, Команда 2, Команда 3, Команда 4, Команда 5 с использованием конвейера. Рассмотрим четвёртый такт конвейера, который выделен на рисунке. Команда 1 уже полностью выполнена, Команда 2 находится на этапе записи данных (ЗАП), Команда 3 - на этапе исполнения (ИСП), Команда 4 - на этапе чтения данных (ЧТН), а к выполнению Команды 5 процессор ещё не приступал. Из-за того, что на каждом такте процессор выполняет с помощью конвейера сразу несколько частей (этапов) команд, все пять команд выполняются за 7 тактов, а не за 15, как было бы при последовательном исполнении этой же цепочки команд. Это оказывается возможным в виду того, что процессор параллельно выполняет различные этапы команд.

    КОНФЛИКТЫ ПРИ КОНВЕЙЕРНОМ ИСПОЛНЕНИИ МАШИННЫХ КОМАНД

    Результаты выполнения команд используются следующими командами программы. Посмотрим, как это простое соображение согласуется с конвейером.

    Приведённый выше пример показывает, что Команда 3 не сможет использовать результаты Команды 2, поскольку свои входные данные она читает раньше, чем Команда 2 записывает свои результаты. В том случае, когда Команде 3 действительно нужны результаты Команды 2, Команда 2 и Команда 3 называются зависимыми.

    Ситуации, возникающие при конвейеризованном исполнении команд, которые препятствуют корректному выполнению очередной команды, называются конфликтами.

    Конфликты бывают следующих видов.

  • Конфликт по данным между зависимыми машинными командами заключается в том, что на конвейере одновременно находятся на разных стадиях выполнения команды, которые могут быть корректно исполнены лишь строго последовательно.
  • Конфликт по ресурсам возникает в ситуации, когда двум командам на конвейере одновременно нужен доступ к какому-либо блоку процессора, с которым в один момент времени может работать только одна команда.
  • Конфликт по управлению заключается в том, что следующая команда на конвейере является условным переходом, но условие для него ещё не вычислено предыдущей командой и не понятно, какую ветку условного оператора следует загружать на конвейер.
  • Поскольку конфликты по данным являются распространённой на практике проблемой, приведём ряд примеров таких конфликтов:

  • следующая команда на конвейере должна читать данные на выходе предыдущей, но предыдущая ещё не закончила их обработку: при этом нарушается зависимость "чтение после записи";
  • следующая команда записывает данные, но они используются (читаются) предыдущей командой, то есть при этом нарушается зависимость "запись после чтения";
  • следующая команда записывает (в регистры процессора или в оперативную память) свои результаты раньше предыдущей, из-за чего предыдущая потом может перезаписать эти результаты: при этом нарушается зависимость "запись после записи".
  • Сделаем замечание относительно третьего вида конфликтов (по управлению) отметим. Такие конфликты дополнительно осложняются тем обстоятельством, что заранее не ясно, в какое место программы произойдёт условный переход, и поэтому не понятно, из какой его ветки загружать на конвейер следующие команды. В связи с этим во многих процессорах есть система предсказания условных переходов, которая собирает статистику по переходам и выбирает наиболее вероятный вариант. В случае ошибки предсказания конвейер очищается от частично обработанных команд, что вызывает задержки.

    Для того чтобы программа могла работать корректно с применением конвейера, конфликтные ситуации требуется обрабатывать, то есть обнаруживать и разрешать. Обнаружение конфликтов оказывается непростой задачей, которую мы не будем здесь рассматривать. Разрешение конфликтов может выполняться различными способами. Самым простым способом является "торможение" конвейера (pipeline stall) с целью задержки выполнения следующей команды до момента исчерпания конфликта. Но, во-первых, конфликтную ситуацию требуется обнаружить, что не просто, во-вторых, задержки уменьшают преимущества конвейера.

    Выделим следующие подходы к обработке конфликтов.

  • Статическое переупорядочивание машинных команд при компиляции программ с языков высокого уровня в машинный код.
  • "Разнесение" конфликтующих команд при компиляции на безопасное расстояние друг от друга с помощью вставки необходимого количества специальной команды NOP (No Operation). Команда NOP ничего не делает, но замедляет работу программы на один такт, в некоторых процессорах - и на большее число тактов.
  • Динамическая обработка конфликтов во время исполнения программы - идентификация и разрешение конфликтов выполняется в момент выполнения программы. При этом процессор задерживает выполнение зависимых команд (как, например, Intel 80486), а также самостоятельно переупорядочивает команды, чтобы исключить конфликты с минимизацией потери времени (так действуют процессор Intel Pentium и последующие процессоры семейств Intel x86).
  • ОСОБЕННОСТИ ОБРАБОТКИ КОНФЛИКТОВ В CISC- И RISC-АРХИТЕКТУРАХ

    Рассмотрев общие подходы к обработке конфликтов на конвейерах, остановимся теперь на особенностях этой процедуры в RISC- и CISC-процессорах. Свойства машинного языка оказывают определяющее влияние на то, каким образом можно организовать конвейерное исполнение машинного кода. Как следствие, различия между CISC- и RISC-процессорами приводят к использованию различных подходов к обработке конфликтов.

    CISC-процессоры делают акцент на динамической обработке конфликтов, поскольку в CISC-процессорах команды имеют различную сложность и время работы, задействуют разные блоки процессора и обращаются к операндам различного вида. Кроме того, в некоторых семействах динамическая обработка конфликтов является единственным возможным решением, поскольку механизм конвейеризации в них появился не сразу, и требовалась совместимость с предыдущими версиями процессоров, то есть механизм конвейеризации не является глубоко интегрированным в процессор.

    Рассмотрим пример. Первым конвейеризованным процессором семейства Intel x86 был процессор Intel 80486. Для того, чтобы у него была возможность корректно и по возможности быстро исполнять машинный код, предназначенный для предыдущих версий не конвейеризованных процессоров, ничего не оставалось, кроме как "научиться" разрешать конфликты динамически. Ни на какие "подсказки" компилятора он при этом рассчитывать не мог, ведь существующий машинный код для предыдущих процессоров семейства Intel x86 про конвейеризацию ничего не знал.

    Реализация динамической обработки конфликтов в CISC-процессорах требует значительного усложнения конвейера. Для сравнения можно представить себе, как усложнился бы конвейер на машиностроительном заводе, если бы на нём приходилось одновременно собирать бульдозеры, комбайны и танки, вдобавок разных моделей. Отметим, что иногда компиляторы и низкоуровневые программисты всё же помогают CISC-процессорам, создавая максимально "безконфликтный" машинный код. Это положительно сказывается на скорости работы программ на старых процессорах, которые, как, например, Intel 80486, умеют при обнаружении конфликтов лишь задерживать выполнение команд, но не переупорядочивать их.

    Машинный код RISC-процессоров, с точки зрения способа хранения команд в оперативной памяти, оказывается проще, чем у CISC-процессоров. Команды RISC-процессоров выполняются за одинаковое число тактов, а более простой машинный язык упрощает обнаружение и обработку конфликтов. Некоторые современные RISC-процессоры (например, процессоры семейства ARM), также как и CISC-процессоры, обнаруживают конфликты и могут переупорядочивать исполнение команд, минимизируя таким образом потерю производительности из-за конфликтов. Другие же RISC-процессоры вовсе не обрабатывают конфликты, например, процессоры семейства MIPS (название семейства этих процессоров расшифровывается как Microprocessor without Interlocked Pipelined Stages, то есть микропроцессор без блокировок на конвейере). Такие процессоры требуют, чтобы подготовленный для них машинный код не содержал потенциальных конфликтов при конвейеризации. Эту "бесконфликтность" машинного кода обеспечивает компилятор.

    В таблице 10.1 представлен пример, показывающий, как может отличаться машинный код одной и той же программы для и CISC- и RISC-процессоров. В рамках этого примера представлен фрагмент программы на языке C, а также машинный код, порождённый для него под процессоры семейств ARM (CISC) и MIPS (RISC). В случае с MIPS-процессором компилятор в некоторых местах вынужден генерировать инструкции NOP для предотвращения конфликтов при конвейеризации, а процессор ARM справляется с конфликтами самостоятельно.

    Результаты трансляции для ARM и MIPS: обработка конфликтов процессором и компилятором
    C-программа Трансляция для ARM Трансляция для MIPS
    Результат Комментарий Результат Комментарий
    int a,b; ldr r3, [fp, #-8]; ldr r2, [fp, #-12] Загрузка переменных: а - в регистр r3, b - в регистр r2. lw $3,8($fp); lw $2,12($fp) Загрузка переменных: а - в регистр $3, b - в регистр $2.
    if (a>b) Nop Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде
    cmp r3, r2 Сравнение регистров r3 и r2 slt $2,$2,$3 Если $2< $3, загрузить 1 в регистр $2, иначе загрузить 0 в регистр $2
    ble .L2 Условный переход (если результат последнего сравнения был "\le ") на следующую за оператором if команду beq $2,$0,$L2 Условный переход (если $2 = $0) на следующую за оператором if команду. Спец. регистр $0 всегда содержит значение 0
    Nop Конфликт по управлению: эта команда всегда попадает на конвейер до того, как становится известно, выполнит ли предыдущая команда переход
    \{a = b;\} ldr r2, [fp, #-12] Загрузка переменной b в регистр r2 lw $2,12($fp) Загрузка переменной b в регистр 2
    nop Конфликт по данным: необходимо доп. время для окончания загрузки переменной b в регистр $2, чтобы использовать его в след. команде
    str r2, [fp, #-8] Выгрузка регистра r2 в переменную a sw $2,8($fp) Выгрузка регистра $2 в переменную a
    b = 0; L2: Метка .L2 - адрес следующей за оператором if команды $L2: Метка $L2 - адрес следующей за оператором if команды
    mov r2, #0; str r2, [fp, #-12] Загрузка 0 в регистр r2, а следом выгрузка из регистра r2 в переменную b sw $0,12($fp) Выгрузка из спец. регистра $0 (всегда содержит 0) в переменную b

    ПАРАЛЛЕЛЬНОЕ ИСПОЛНЕНИЕ КОМАНД

    Как уже многократно указывалось, повышение производительности современных процессоров осуществляется в значительной степени за счёт распараллеливания. Одним из популярных способов распараллеливания является конвейеризация. Но хотя конвейеризация и позволяет значительно ускорить исполнение программ, максимального эффекта она достигает лишь в идеальных ситуациях, когда конфликты не возникают, или когда удаётся переупорядочить команды таким образом, чтобы эти конфликты исчерпать. Для дальнейшего повышения производительности применяются (иногда в сочетании с конвейеризацией) более сложные решения.

    В качестве первой техники параллельного исполнения команд процессором рассмотрим суперскалярность - возможность одновременно выполнять несколько машинных команд за счёт наличия в процессоре нескольких однотипных функциональных блоков (арифметико-логических устройств, математических сопроцессоров и т.д.) В семействе Intel x86 первым процессором, где была реализована суперскалярность, был процессор Intel Pentium (1993 год). Этот процессор содержал два арифметико-логических устройства, которые позволяли исполнять одновременно две соседние команды, если они не зависели друг от друга. При этом независимые команды одновременно обрабатывались двумя разными конвейерами. Для этого компиляторы стремились генерировать машинный код, соседние команды которого не зависели бы друг от друга.

    Более продвинутой техникой параллельного исполнения машинных команд является внеочередное исполнение (Out of Order Execution). Эта техника позволяет исполнять команды программы не в порядке следования, а в порядке готовности к выполнению. При использовании внеочередного исполнения некоторое множество команд программы исполняется тогда, когда для всех этих команд готовы нужные им входные данные. Первой ЭВМ, в которой был реализован механизм внеочередного исполнения команд, был суперкомпьютер CDC 6600 компании Cray Research, созданный в 1963 году.

    Приведём следующий пример. Пусть в программе подряд идут следующие команды:

     (1) a = b / c (2) d = b + a (3) x = y * z 

    Деление двух чисел (команда (1)) является сложной командой, которая выполняется существенно дольше, чем, например, сложение. Вспомните алгоритм деления "в столбик": в нём последовательно выполняется много операций и проверок. Процессор выполняет деление практически так же, но только в двоичной системе. В то же время, от результатов команды (1) зависит выполнение команды (2), а выполнение команды (3) не зависит. Поэтому можно приступить к выполнению команд (1) и (3) одновременно, а команду (2) начать выполнять после вычисления значения a.

    Рассмотрим общий сценарий внеочередного исполнения команд в современных процессорах на примере распространённого известного процессора Intel Core i7. После выборки процессором очередной команды для исполнения эта команда делится на микрооперации - простые действия, такие как "сложить два числа", "записать значение в регистр", "выдать адрес ячейки на шину адреса" и подобные им. Микрооперации помещаются в специальный буфер переупорядочивания (Reorder Buffer), в котором они упорядочиваются так, чтобы, с одной стороны, не нарушить корректность программы, а с другой стороны оптимально загрузить блоки процессора. Причём в буфере находятся и переупорядочиваются одновременно микрооперации нескольких идущих подряд команд. Чтобы получить больше свободы для переупорядочивания команд, процессор выполняет переименование регистров: для разных микроопераций, работающих с одними и теми же регистрами, назначаются новые регистры. Получившиеся микрооперации выполняются шестью блоками процессора - тремя АЛУ и тремя блоками доступа к памяти. При обнаружении условного перехода одновременно начинают обрабатываться обе ветви then и else, то есть используется спекулятивное исполнение программы. Но после фактического вычисления условия фиксируются только результаты команд из выбранной в переходе ветки.

    Компания Intel реализовала внеочередное исполнение команд задолго до запуска в производство современных многоядерных процессоров, в рамках процессоров Intel Pentium Pro в начале 2000-х годов. Было замечено, что блоки процессора выполняли микрооперации эффективно, но часто простаивали, ожидая результатов выборки машинных команд из оперативной памяти. Для того, чтобы оптимально загрузить эти блоки, была создана технология HyperThreading. Она выбирала машинные команды из оперативной памяти в двух параллельных потоках. Одноядерный процессор с применением технологии HyperThreading, фактически, начинает работать, как двухъядерный. Технология HyperThreading была запатентована сотрудником компании Sun Microsystems в 1994 году, но впервые была реализована в лишь 2002 году (Intel).

    Вопросы

  • Дайте определение конвейера.
  • Дайте определение конфликта на конвейере.
  • Перечислите виды конфликтов конвейера.
  • Что такое зависимые команды?
  • Что такое конфликт по ресурсам?
  • Что такое конфликт по управлению?
  • Перечислите способы преодоления конфликтов.
  • Что такое статическое переупорядочивание команд при решении конфликтов?
  • Расскажите про использование команды NOP для разрешения конфликтов.
  • Что такое динамическое разрешение конфликтов?
  • Какой подход используется при разрешении конфликтов на конвейерах CICS-процессоров и почему?
  • Какой подход используется при разрешении конфликтов на конвейерах RICS-процессоров и почему?
  • Зачем нужна система предсказания условных переходов?
  • Как CISC-процессоры разрешают конфликты конвейера?
  • Каковы особенности разрешения конфликтов в RISC-процессорах?
  • Почему конвейеризация RISC-процессоров проще, чем CISC?
  • Что такое суперскалярность?
  • Что такое внеочередное исполнение машинных команд?
  • Расскажите про технологию HyperThreading.
  • Литература

  • Гуров В.В. Архитектура микропроцессоров / Интернет-университет информационных технологий, Интернет-Университет Информационных Технологий (ИНТУИТ), Бином. 2010. 272 с.
  • Хорошевский В.Г. Архитектура вычислительных систем.: Учеб. пособие. 2-e изд., перераб. и доп. M.: Изд-во МГТУ им. H.Э. Баумана, 2008. 520 c.
  • Таненбаум Э., Остин Т. Архитектура компьютера. 6-е изд. СПб.: Питер, 2013. 816 с.
  • Patterson D. A., Ditzel D. R. The case for the reduced instruction set computer //ACM SIGARCH Computer Architecture News. 1980, vol. 8, No 6, P. 25-33.
  • Вернуться к учебному плану