Тип процессорной архитектуры с длинной машинной командой называется
Недавно здесь, на Хабре, появилось несколько статей о достоинствах/недостатках VLIW архитектуры по сравнению с CISC и RISC. Но ведь и те и другие далеко не идеальны! Суперскалярные процессоры вынуждены тратить ресурсы на попытки распараллеливания последовательных команд и предугадывание возможных переходов, что не только ведет к перерасходу вычислительных ресурсов, но и просто небезопасно (вспоминаем Spectre и Meltdown).
VLIW предполагает выполнение длинных командных слов внутри которых может предполагаться выполнение до 23 (Эльбрус) параллельных инструкций. Однако и эта структура не лишена недостатков : длительность выполнения командного слова определяется самой медленной инструкцией (например деления или обращения к памяти); приходится для каждого ядра держать большой резерв вычислительной мощности в расчете на необходимость параллельного выполнения максимального числа инструкций; очень ограниченные возможности по распараллеливанию инструкций обращения к памяти; ограниченность параллелизма только шагом в одну инструкцию; необходимость предсказания перехода (как и в суперскалярных процессорах); и невозможность динамически разносить инструкции на параллельные потоки (количество одновременно исполняемых инструкций жестко прописано на этапе компиляции программы и не может быть изменено для процессоров с разными возможностями).
В этой статье я представлю свои мысли по альтернативной архитектуре процессора, которая должна объединить в себе достоинства RISC, CISC и VLIW архитектур.
Итак, представляю вашему вниманию процеонную архитектуру процессора :
Пул процеонов (от Processor и Nucleon (протон или нейтрон)) – это набор SuperRISC процессоров, находящихся в ожидании или в выполнении линейки команд. Ядро – это управляющий процессор для выполнения потоков команд и управления обращениями к внешним данным. Многоканальная кэш-память обеспечивает быстрый доступ к наиболее часто необходимым данным. Блок внешних обменов транслирует запросы к внешней (относительно микропроцессора) памяти, внешним устройствам и обеспечивает приём внешних прерываний.
Ниже приводятся принципы для новой архитектуры (процеонная структура).
Ядро не выполняет команды, а только управляет потоком команд и обращениями к памяти.
Команды выполняют процеоны – это SuperRISC процессорные ядра с ограниченным набором команд (ограничение прежде всего управлением исполнения программы).
Ядро обрабатывает целый блок команд – параграф (в дальнейшем под этим понимается замкнутый набор команд, после выполнения которых изменяется состояние программы. Можно сказать что параграф для ядра процессора – это команда), который содержит отдельно данные и отдельно одну или более линеек команд.
Параграф начинается префиксом и заканчивается окончанием. Префикс и окончание обрабатываются ядром.
Префикс определяет сколько линеек в параграфе, какой они длины и типа. Самая первая линейка – это данные для команд находящихся в рабочих линейках. Благодаря этому можно отделить команды от данных и сделать фиксированную длину каждой команды.
Окончание определяет какие результаты помещаются в регистры по окончанию работы всех команд параграфа. А так же какой параграф выполняется следующим.
Линейки команд выполняются параллельно (по возможности, если есть свободные процеоны). А команды в линейке – последовательно.
Процеоны (а значит и линейки команд !) могут быть разными (например процеон для целочисленных 32-битных операций, для работы с числами с плавающей запятой, для работы со строковыми данными, для работы с двойными словами и т.д.). Однако формат команд для всех процеонов един.
Система команд процеонов не содержит команд перехода, за исключением команды выполнить/пропустить следующую команду по условию и команд завершить линейку, завершить параграф. Флаги выполнения в процеонах не используются (за исключением переноса в целочисленных операциях для учета в следующей команде).
Процеоны не жестко закреплены за ядром, а выделяются для выполнения конкретного параграфа команд по мере необходимости. Если процеонов оказывается меньше, чем линеек в параграфе, то часть линеек выполняются последовательно.
Прерывание параграфа возможно только при неустранимой ошибке вызывающей аварийное завершение. В остальных случаях прерывание откладывается до окончания параграфа. Так например деление на 0 не вызывает немедленного прерывания параграфа, а только завершение одной линейки, в которой произошла ошибка.
Регистры процессора делятся на 4 группы : Регистры Общего Назначения (РОН (R0-R15) – 16 регистров по 64 бит, копии передаются в процеоны при старте линеек, по окончании линеек их комбинация копируется в ядро), локальные (временные / temporary) регистры процеонов (RT – 16 регистров разрядностью в зависимости от типа процеона, по окончании линейки значения теряются, при начале линейки инициализируются значениями от 0 до 14, последний получает значение -1), регистры данных (RD – доступны только для чтения, инициализируются данными 0-ой линейки или командами отложенной загрузки. Регистры не получившие данные – обнуляются), управляющие регистры (RF – 16 регистров: регистр состояния программы, счетчик команд, регистр следующего параграфа, управления кэш-памятью, регистры копирования, регистр блока коротких обращений к памяти и проч.).
Процеон может обращаться к памяти напрямую только в границах сегмента указанного в регистре блока коротких обращений к памяти (не более 4 ГБ – пространство адресуемое 32 битами, конкретный размер определяется в регистре флагов). Обращения за пределами этого сегмента возможны только в виде команд отложенного обращения к памяти (их выполнение откладывается до момента окончания параграфа).
Для уменьшения обращений в память есть 3 регистра копирования (в блоке управляющих регистров): стартовый адрес, конечный адрес и количество байт для копирования.
Все обращения к памяти и трансляцию адресов выполняет ядро. Процеон не имеет кэша и не преобразует виртуальные адреса в реальные физические.
Рассмотрим структуру параграфа :
Наименование
Структура
<Код префикса><Длина Данных (K) ><Количество линеек (N)> <Тип линейки 1> <Длина линейки 1 (L1)>…. <Тип линейки N> <Длина линейки N(Ln)>
<Слово 1> ….. <Слово K>
<Команда 1>…..<Команда L1>
<Команда 1>…..<Команда Ln>
Код префикса (4 бита + 3 бита резерв ) – код определяющий начало параграфа и его тип
Длина Данных (5 бит) – количество 64-разрядных данных для регистров данных (K – от 0 до 16, часть регистров данных может быть инициализирован через отложенную команду чтения или остаться 0)
Количество линеек (4 бита) – количество линеек в блоке команд (N – от 1 до 16)
Тип линейки (4 бита) – какой нужен тип процеона для выполнения данной линейки
Длина линейки (4 бита) – количество команд в соответствующей линейке команд (L от 1 до 16)
Код окончания (8 бит) – код определяющий как будет завершен параграф и как будут объединены регистры из процеонов в ядре.
Как происходит отработка ядром параграфа по типу 1 (базовому) при достаточном количестве процеонов:
Ядро выбирает префикс содержащий Код префикса, Длину Данных, Количество линеек (итого 16 бит)
Выбирает байты с длиной и типом линеек (в соответствии с Количеством линеек)
Считывает Данные в блок регистров данных (или заполняет данными из команд отложенного чтения)
Запрашивает нужные процеоны из общего пула процеонов
Инициализирует блок общих регистров каждого процеона копией РОН ядра, а Регистр Следующего Параграфа адресом следующим за Окончанием. Блок временных регистров каждого процеона инициализируется от 0 до 14 (RT0 - RT14) и -1 (RT15).
Передает каждому процеону его линейку команд
Ждет окончания всех линеек (в это время ядро может переключиться на выполнение другого потока – по аналогии с hyper-threading technology в процессорах x86)
Собирает данные со всех линеек по условию (код окончания определяет процедуру сбора РОН, управляющие регистры заполняются строго последними поступившими данными)
Передает управление по адресу из Регистра Следующего Параграфа
Как происходит отработка ядром параграфа по типу 2 (параллельный цикл). В этом случае в параграфе содержится только одна линейка команд, но которая будет выполняться параллельно на нескольких процеонах, причем количество процеонов может быть меньше количества итераций выполнения линейки. Рассмотрим как это должно происходить.
Ядро выбирает префикс параграфа типа 2, в котором вместо количества линеек указан РОН в котором содержится количество итераций (из-за ограничения на выполнение прерывания внутри параграфа число итераций ограничено 256).
Считывает Данные в блок регистров данных
Запрашивает процеоны из пула в количестве равным количеству итераций.
В зависимости от реально полученного числа процеонов, выделяет каждому процеону выполнение своего количества итераций (например, количество итераций 16, а выделено 4 процеона, значит 1-й выполняет итерации с 16 по 13, 2-й - с 12 по 9, 3-й - с 8 по 5, 4-й - с 4 по 1)
При каждом входе каждый процеон инициализирует свои регистры, регистр содержащий количество итераций, инициализируется номером итерации
Каждый процеон выполняет отведенные ему итерации
Ядро дожидается окончания выполнения всех итераций и решает какие данные поместить в РОН (в зависимости от кода окончания происходит либо выбор регистров определенного процеона, либо арифметическое/логическое действие над регистрами, либо их комбинация. Подробнее механизм заполнения РОН результатами рассматривается ниже)
По окончании передается управление по адресу из Регистра следующего Параграфа (данные в него могут быть занесены командой процеона)
Все команды (инструкции) всех процеонов имеют одинаковую длину и формат. Формат инструкций следующий : одна команда - 24 бита, 7 бит КОП, 5 бит - приемник, 6+6 бит - источник1 и источник2. Исключение – команда сравнения. В ней вместо приемника – условие выполнения следующей команды. В случае операций записи в память вместо регистра-приемника указывается регистр-источник данных, а 2 регистра-источника суммируются в адрес памяти.
Структура памяти и обращений к ней.
Все операции к памяти процеоны выполняют через обращение к ядру. Операции записи ядро выполняет через кэш с обратной записью (т.е. программа не ждет выполнения записи). Результат операций чтения так же является отложенным – данные попадают в регистры к моменту начала следующего параграфа, благодаря чему процеоны не простаивают в ожидании данных. Исключение – обращение к памяти в ограниченном сегменте, адрес которого указан в регистре быстрых обращений к памяти (РБОП), а в регистре флагов находится длина маски изменяемых бит адресов (в пределах которых процеоны могут обращаться напрямую к памяти и дожидаться данных). Это позволяет заранее загрузить в кэш блок данных на который указывает РБОП и размером определяемым в регистре флагов. Если в кэше такой блок не помещается, то он должен быть размещен в основной памяти, но ни в коем случае не в виртуальной, чтобы при обращении к нему не произошло прерывания из-за отсутствия данных в ОЗУ.
Так же для разгрузки вычислительных мощностей процеонов специально для копирования данных в ядро добавлена логика копирования – 3 управляющих регистра: Регистр Начального Адреса (РНА), Регистр Конечного Адреса (РКА) и Регистр Длины Копирования (РДК). При инициализации РДК, ядро начинает операцию копирования из адреса РНА+РДК на адрес РКА+РДК, затем уменьшает РДК на количество скопированных байт и повторяет операцию копирования, пока РДК не станет равен 0. Такая операция копирования может выполняться параллельно с основной программой, не привязываясь к началу или окончанию параграфа. Признаком окончания такой операции является 0 в РДК.
Механизм заполнения РОН результатами выполнения параграфа
По окончании выполнения параграфа в ядре оказываются копии РОН каждой выполненной линейки (или итерации) команд. Ядро должно выбрать те данные которые следует поместить в РОН (замечание по Управляющим Регистрам – в них попадают данные строго по тому, в какой очередности были выполнены команды, поэтому ответственность за заполнение Управляющих Регистров лежит на программисте ! Исключение – сумматор, о нем речь ниже), для этого используется код окончания блока команд.
Особое место среди Управляющих Регистров занимает Сумматор. При окончании параграфа данные занесенные в регистр младшего слова сумматора, суммируются и результат заносится в регистры старшего и младшего слов сумматора.
Преимущества данной схемы:
Распараллеливанием команд занимается компилятор, а не процессор, что позволяет делать это более оптимально
Распараллеливание происходит на уровне элементарных RISC команд (аналог микрокоманд в x86 процессорах), причем не на уровне отдельных команд, а в виде линеек последовательно выполняющихся команд, что позволяет параллельно выполнять команды с разным временем выполнения (например в одной линейке выполняется команда деления, а в другой за это время может быть выполнено несколько простых арифметических команд)
Есть возможность динамически изменять количество одновременно выполняемых линеек команд (в зависимости от свободных ресурсов выполнять линейки либо последовательно, либо параллельно)
Вычислительные мощности (процеоны) могут быть использованы любым ядром (динамическое перераспределение вычислительной мощности между задачами)
Экономия энергии (возможность временно отключать неиспользуемые процеоны)
Благодаря отсутствию команды условного перехода (на уровне ядра) отпадает потребность в блоке предсказания переходов. Адрес следующего параграфа может быть получен заранее, до окончания выполнения текущего параграфа
Удобное масштабирование и специализация процессоров – достаточно увеличить или уменьшить число процеонов соответствующего типа
Упрощается одновременное выполнение ядром нескольких задач (на время выполнения процеонами одной задачи ядро может запускать на выполнение другую задачу)
Оптимизируется доступ к памяти – процеонам требуется для чтения блок в памяти ограниченного размера. Операции чтения из других адресов (оказавшихся в виртуальной памяти) могут быть отложены и не приводят к немедленному прерыванию задачи
Благодаря разделению процессора на сравнительно одинаковые блоки, упрощается проектирование микропроцессоров.
Заключение
В этой краткой статье сделана попытка описания альтернативной структуры (в рамках классического «фон-Неймановского» процессора) центрального процессора общего назначения с упором на внедрение параллельного исполнения команд на уровне приближенном к микрокомандам. Конечно данный подход не является единственно возможным, но принципы изложенные выше (вынесение из ядра процессора вычислительных модулей, возможность динамически перераспределять ресурсы между ядрами, параллельное выполнение целых линеек команд (а не отдельных команд), динамическое выделение ресурсов для параллельных вычислений, отсутствие потребности в блоке прогнозирования ветвлений и введение команд отложенного чтения из памяти) заслуживают рассмотрения.
Прежде чем рассмотреть основные виды архитектур процессоров, необходимо понять, что это такое. Под архитектурой процессора обычно понимают две совершенно разные сущности.
С программной точки зрения архитектура процессора — это совместимость с определённым набором команд (Intel x86), их структуры (система адресации, набор регистров) и способа исполнения (счётчик команд).
Говоря простым языком, это способность программы, собранной для архитектуры x86, работать практически на любой x86-совместимой системе. При этом такая программа не будет работать, например, на ARM системе.
С аппаратной точки зрения архитектура процессора — это некий набор свойств и качеств, присущий целому семейству процессоров (Skylake – процессоры Intel Core 5 и 6 поколений).
Если тема кажется сложной, можно начать со статьи о том, чем CPU отличается от GPU.
Виды архитектур
В этой статье мы рассмотрим самые распространенные и актуальные архитектуры с программной точки зрения, кроме узкоспециализированных (графических, математических, тензорных).
CISC (англ. Complex Instruction Set Computer — «компьютер с полным набором команд») — тип процессорной архитектуры, в первую очередь, с нефиксированной длиной команд, а также с кодированием арифметических действий в одной команде и небольшим числом регистров, многие из которых выполняют строго определенную функцию.
Самый яркий пример CISC архитектуры — это x86 (он же IA-32) и x86_64 (он же AMD64).
В CISC процессорах одна команда может быть заменена ей аналогичной, либо группой команд, выполняющих ту же функцию. Отсюда вытекают плюсы и минусы архитектуры: высокая производительность благодаря тому, что несколько команд могут быть заменены одной аналогичной, но большая цена по сравнению с RISC процессорами из-за более сложной архитектуры, в которой многие команды сложнее раскодировать.
RISC (англ. Reduced Instruction Set Computer — «компьютер с сокращённым набором команд») — архитектура процессора, в котором быстродействие увеличивается за счёт упрощения инструкций: их декодирование становится более простым, а время выполнения — меньшим. Первые RISC-процессоры не имели даже инструкций умножения и деления и не поддерживали работу с числами с плавающей запятой.
По сравнению с CISC эта архитектура имеет константную длину команды, а также меньшее количество схожих инструкций, позволяя уменьшить итоговую цену процессора и энергопотребление, что критично для мобильного сегмента. У RISC также большее количество регистров.
Примеры RISC-архитектур: PowerPC, серия архитектур ARM (ARM7, ARM9, ARM11, Cortex).
В общем случае RISC быстрее CISC. Даже если системе RISC приходится выполнять 4 или 5 команд вместо одной, которую выполняет CISC, RISC все равно выигрывает в скорости, так как RISC-команды выполняются в 10 раз быстрее.
Отсюда возникает закономерный вопрос: почему многие всё ещё используют CISC, когда есть RISC? Всё дело в совместимости. x86_64 всё ещё лидер в desktop-сегменте только по историческим причинам. Так как старые программы работают только на x86, то и новые desktop-системы должны быть x86(_64), чтобы все старые программы и игры могли работать на новой машине.
Для Open Source это по большей части не является проблемой, так как пользователь может найти в интернете версию программы под другую архитектуру. Сделать же версию проприетарной программы под другую архитектуру может только владелец исходного кода программы.
MISC (англ. Minimal Instruction Set Computer — «компьютер с минимальным набором команд»).
Ещё более простая архитектура, используемая в первую очередь для ещё большего уменьшения итоговой цены и энергопотребления процессора. Используется в IoT-сегменте и недорогих компьютерах, например, роутерах.
Для увеличения производительности во всех вышеперечисленных архитектурах может использоваться “спекулятивное исполнение команд”. Это выполнение команды до того, как станет известно, понадобится эта команда или нет.
VLIW (англ. Very Long Instruction Word — «очень длинная машинная команда») — архитектура процессоров с несколькими вычислительными устройствами. Характеризуется тем, что одна инструкция процессора содержит несколько операций, которые должны выполняться параллельно.
По сути является архитектурой CISC со своим аналогом спекулятивного исполнения команд, только сама спекуляция выполняется во время компиляции, а не во время работы программы, из-за чего уязвимости Meltdown и Spectre невозможны для этих процессоров. Компиляторы для процессоров этой архитектуры сильно привязаны к конкретным процессорам. Например, в следующем поколении максимальная длина «очень длинной команды» может из условных 256 бит стать 512 бит, и тут приходится выбирать между увеличением производительности путём компиляции под новый процессор и обратной совместимостью со старым процессором. Опять же, Open Sourсe позволяет простой перекомпиляцией получить программу под конкретный процессор.
Примеры архитектуры: Intel Itanium, Эльбрус-3.
Виртуальные архитектуры
Из минусов виртуальных архитектур можно выделить меньшую производительность по сравнению с реальными архитектурами. Этот минус нивелируется с помощью JIT- и AOT-компиляции. Однако большим плюсом будет кроссплатформенность.
Дальнейшим развитием этих архитектур стали гибридные архитектуры. Например современные x86_64 процессоры хотя и CISC-совместимы, но являются процессорами с RISC-ядром. В таких гибридных CISC-процессорах CISC-инструкции преобразовываются в набор внутренних RISC-команд. Какое дальнейшее развитие получат архитектуры процессора, покажет только время.
EPIC: явный параллелизм команд
Тот факт, что эти методики не требуют от прикладных программистов дополнительных усилий, имеет крайне важное значение, поскольку данное решение резко отличается от традиционного микропроцессорного параллелизма, который предполагает, что программисты должны переписывать свои приложения. Параллельная обработка на уровне команд является единственным надежным подходом, позволяющим добиться увеличения производительности без фундаментальной переработки приложения.
Одна из целей, которые ставили перед собой при создании EPIC, состояла в том, чтобы сохранить реализованный во VLIW принцип статического создания POE, но и в то же время обогатить его возможностями, аналогичными возможностям суперскалярного процессора, позволяющими новой архитектуре лучше учитывать динамические факторы, традиционно ограничивающие параллелизм, свойственный VLIW. Чтобы добиться этих целей, «идеология» EPIC была построена на некоторых основных принципах.
Аппаратно-программный комплекс VLIW
Архитектура VLIW представляет собой одну из реализаций концепции внутреннего параллелизма в микропроцессорах. Их быстродействие можно повысить двумя способами: увеличив либо тактовую частоту, либо количество операций, выполняемых за один такт. В первом случае требуется применение «быстрых» технологий (например, использование арсенида галлия вместо кремния) и таких архитектурных решений, как глубинная конвейеризация (конвейеризация в пределах одного такта, когда в каждый момент времени задействованы все логические блоки кристалла, а не отдельные его части). Для увеличения количества выполняемых за один цикл операций необходимо на одном чипе разместить множество функциональных модулей обработки и обеспечить надежное параллельное исполнение машинных инструкций, что дает возможность включить в работу все модули одновременно. Надежность в таком контексте означает, что результаты вычислений будут правильными. Для примера рассмотрим два выражения, которые связаны друг с другом следующим образом: А=В+С и В=D+Е. Значение переменной А будет разным в зависимости от порядка, в котором вычисляются эти выражения (сначала А, а потом В, или наоборот), но ведь в программе подразумевается только одно определенное значение. И если теперь вычислить эти выражения параллельно, то на правильный результат можно рассчитывать лишь с определенной вероятностью, а не гарантировано.
Логический слой VLIW-процессора
Эта гипотетическая инструкция имеет восемь операционных полей, каждое из которых выполняет традиционную трехоперандную RISC-подобную инструкцию типа = — — (типа классической команды MOV AX BX) и может непосредственно управлять специфическим функциональным блоком при минимальном декодировании.
Для большей конкретности рассмотрим кратко IA-64 как один из примеров воплощения VLIW. Со временем эта архитектура способна вытеснить x86 (IA-32) не только на рынке, но вообще как класс, хотя это уже удел далекого будущего. Тем не менее, необходимость разработки для IA-64 весьма сложных компиляторов и трудности с созданием оптимизированных машинных кодов может вызвать дефицит специалистов, работающих на ассемблере IA-64, особенно на начальных этапах, как самых сложных.
Наиболее кардинальным нововведением IA-64 по сравнению с RISC является явный параллелизм команд (EPIC), вносящий некоторые элементы, напоминающие архитектуру сверхдлинного командного слова, которые назвали связками (bundle). Так, в обеих архитектурах явный параллелизм представлен уже на уровне команд, управляющих одновременной работой функциональных исполнительных устройств (или функциональных модулей, или просто функциональных устройств, ФУ).
В данном случае связка имеет длину 128bit и включает в себя 3 поля для команд длиной 41bit каждое, и 5-разрядный слот шаблона. Предполагается, что команды связки могут выполняться параллельно разными ФУ. Возможные взаимозависимости, препятствующие параллельному выполнению команд одной связки, отражаются в поле шаблона. Не утверждается, впрочем, что параллельно не могут выполняться и команды разных связок. Хотя, на основании заявленного уровня параллельности исполнения, достигающего шести команд за такт, логично предположить, что одновременно могут выполняться как минимум две связки.
Шаблон указывает какого непосредственно типа команды находятся в слотах связки. В общем случае однотипные команды могут выполняться в более чем одном типе функциональных устройств. Шаблоном задаются так называемые остановки, определяющие слот, после начала выполнения команд которого инструкции последующих полей должны ждать завершения. Порядок слотов в связке (более важные справа) отвечает и порядку байт (Little Endian), однако данные в памяти могут располагаться и в режиме Big Endian (более важные слева), который устанавливается специальным битом в регистре маски пользователя.
Вращение регистров является в некотором роде частным случаем переименования регистров, применяемого во многих современных суперскалярных процессорах с внеочередным спекулятивным («умозрительным») выполнением команд. В отличие от них, вращение регистров в IA-64 управляется программно. Использование этого механизма в IA-64 позволяет избежать накладных расходов, связанных с сохранением/восстановлением большого числа регистров при вызовах подпрограмм и возвратах из них, однако статические регистры при необходимости все-таки приходится сохранять и восстанавливать, явно кодируя соответствующие команды.
Принцип действия VLIW-компилятора
VLIW: обратная сторона медали
Как пример, можно вспомнить знаменитый Crusoe от Transmeta. Принцип его работы фактически состоит в динамической перекомпиляции под VLIW-архитектуру уже скомпилированного x86 кода. Однако, если даже в создании эффективных VLIW-компиляторов для языков высокого уровня разработчики сталкиваются с такими большими сложностями, то что можно сказать о «компиляторе» весьма хаотичного и непредсказуемого машинного кода x86, тем более, что он зачастую опять-таки оптимизирован при компиляции, но в расчете на совсем другие архитектурные особенности.
С другой стороны, даже сам Гордон Мур уже заявил, что рост частоты процессоров в ближайшее время скорее всего перестанет подчиняться сформулированному им эмпирическому правилу, и существенно замедлится. В этой ситуации производительность классических систем рискует довольно скоро упереться в «тупик» физических ограничений, что в еще большей степени подхлестнет развитие альтернативных повышению частоты способов увеличения быстродействия, одним из которых является разработка принципиально новых и хорошо распараллеливающихся алгоритмов, что и позволит проявить себя VLIW во всей красе.
Вместо заключения
Нельзя не отметить тот факт, что предварительное планирование последовательности операций в большем числе случаев, чем для классических процессоров, позволит максимально полно использовать реальную пропускную способность шины данных. Но столь высокая и прогнозируемая эффективность использования интерфейса требует, с другой стороны, принципиально нового подхода к построению системы.
Оглядываясь назад, можно предположить с довольно большой вероятностью, что Transmeta «погубило» нежелание использовать в полной мере существующие современные технологии памяти. Вполне очевидно, что при динамической компиляции приложений нагрузка на ОЗУ увеличится в разы. Иными словами, используя подсистему памяти с невысокой пиковой пропускной способностью, Transmetа обрекла себя на огромный проигрыш в приложениях, интенсивно обращающихся к системному ОЗУ, тогда как в производительности приложений, большую часть времени проводящих внутри процессора, практически паритет. Будущий путь Transmeta, рассуждая логически, должен быть направлен на расширение количества функциональных устройств процессора с возможностью параллельного исполнения двух и более веток программы (разных программ) в одном ядре, используя более масштабируемую и быстродействующую системную память.
Понятие архитектуры процессора не имеет единого толкования, поскольку под ним понимаются две различные сущности. С программной позиции она представляет собой совместимость процессора с конкретным набором команд, его способность выполнять определённый набор кодов. То есть это способность программы, которая была собрана для архитектуры семейства x86, функционировать на всех x86-совместимых системах. На ARM системе такая программа работать не будет.
С аппаратной позиции архитектура процессора, называемая иногда микроархитектурой, является набором свойств, характерным для всего семейства процессоров и отражающим базовые особенности его внутренней организации. К примеру, микроархитектура процессоров Intel Pentium имела обозначение Р5, а процессоры Pentium 4 относились к NetBurst. После закрытия Intel микроархитектуры Р5 для производителей AMD разработала архитектуру К7 и К8 для процессоров Athlon, Athlon XP и Athlon 64 соответственно.
CISC
CISC-архитектура (Complex Instruction Set Computer) относится к процессорам с полным набором команд. Она имеет нефиксированную длину команд, отличается кодированием арифметических действий в единой команде и малым количеством регистров, большинство из которых выполняет только выделенную функцию.
CISC реализована во множестве типов микропроцессоров, таких как Pentium, которые выполняют большое количество разноформатных команд (порядка 200-300), применяя более десяти различных способов адресации. Командная система может включать несколько сотен команд различного уровня сложности или формата (от 1 до 15 байт).
Всё это делает возможным реализовывать эффективные алгоритмы для различных задач. В качестве примеров CISC-архитектуры, используемой преимущественно для десктопных версий, можно привести следующие процессоры:
- x86 (IA-32, сокращенное от "Intel Architecture, 32-bit") - ;
- x86_64 (AMD64);
- Motorola MC680x0;
- мейнфреймы zSeries.
В CISC-процессорах каждую из команд возможно заменить на аналогичную ей либо на группу выполняющих такую же функцию команд. Это формирует как достоинства, так и недостатки архитектуры: она обладает высокой производительностью благодаря возможности замены команд, но большей стоимостью в сравнении с RISC, что связано с усложнённой архитектурой, в которой существует множество сложных для раскодирования команд.
RISC-архитектура (Reduced Instruction Set Computer) относится к процессорам с сокращённым набором команд. В ней быстродействие увеличивается посредством упрощения инструкций: за счёт того, что их декодирование становится проще, уменьшается время исполнения. Изначально RISC-процессоры не обладали инструкциями деления и умножения и не могли работать с числами, имеющими плавающую запятую. Их появление связано с тем, что в CISC достаточно много способов адресации и команд использовались крайне редко.
Система команд в RISC состоит из малого числа часто применяемых команд одного формата, которые можно выполнить за единичный такт центрального процессора. Более сложные и редко применяемые команды выполняются на программном уровне. При этом, благодаря значительному увеличению скорости реализации команд, средняя производительность RISC-процессоров выше, чем у CISC.
Благодаря сокращению аппаратных средств, используемых для декодирования и реализации сложных команд, достигается значительное упрощение и снижение стоимости интегральных схем. В то же время возрастает производительность и снижается энергопотребление, что особенно актуально для мобильного сегмента. Эти же достоинства служат причиной использования во многих современных CISC-процессорах, например в последних моделях К7 и Pentium, RISC-ядра. Сложные CISC-команды заранее преобразуются в набор простых RISC-операций, которые оперативно выполняются RISC-ядром.
Характерными примерами RISC-архитектур являются:
- PowerPC;
- DEC Alpha;
- ARC;
- AMD Am29000;
- серия архитектур ARM;
- Atmel AVR;
- Intel i860 и i960;
- BlackFin;
- MIPS;
- PA-RISC;
- Motorola 88000;
- SuperH;
- RISC-V;
- SPARC.
RISC быстрее CISC, и даже при условии выполнения системой RISC четырёх или пяти команд вместо единственной, выполняемой CISC, RISC выигрывает в скорости, поскольку его команды выполняются в разы оперативнее. Однако CISC продолжает использоваться. Это связано с совместимостью: x86_64 продолжает лидировать в десктоп-сегменте, а поскольку старые программы могут функционировать только на x86, то и новые десктоп-системы должны быть x86(_64), чтобы дать возможность старым программам работать на новых устройствах.
Для Open Source это не проблема, ведь пользователь может найти в сети версию программы, подходящую для другой архитектуры. Однако создать версию проприетарной программы для другой архитектуры получится только у владельца исходного кода.
MISC
MISC-архитектура (Minimal Instruction Set Computer) является процессором с минимальным набором команд. Она отличается ещё большей простотой и используется для ещё большего снижения энергопотребления и итоговой стоимости процессора. MISC-архитектура применяется в IoT-сегменте и компьютерах малой стоимости вроде роутеров. Первой вариацией такого процессора стал MuP21.
В основе MISC-процессоров лежит укладка ряда команд в единое большое слово, что позволяет параллельно обрабатывать несколько потоков данных. MISC применяет стековую модель устройства и базовые слова языка Forth. Процессоры этой архитектуры отличаются малым числом наиболее востребованных команд и использованием длинных командных слов, что позволяет получить выполнение ряда непротиворечивых команд за единый цикл работы процессора. Порядок исполнения команд определяется так, чтобы максимально загрузить маршруты, пропускающие потоки данных.
Все вышеназванные архитектуры могут применять «спекулятивное исполнение команд», то есть исполнение команды заранее, когда ещё неизвестна её необходимость. Это позволяет увеличить производительность.
VLIW
VLIW-архитектура (Very Long Instruction Word) относится к микропроцессорам, применяющим очень длинные команды за счёт наличия нескольких вычислительных устройств. В отдельных полях команды присутствуют коды, которые обеспечивают реализацию различных операций. Одна команда в VLIW может исполнить одновременно несколько операций в разных узлах микропроцессора. Формированием таких длинных команд занимается соответствующий компилятор во время трансляции программ, которые написаны на высокоуровневом языке.
VLIW-архитектура, являясь достаточно перспективной для разработки нового поколения высокопроизводительных процессоров, реализована в некоторых современных микропроцессорах:
- Intel Itanium;
- AMD/ATI Radeon (с R600 до Northern Islands);
- серия «Эльбрус».
VLIW схожа с архитектурой CISC, имея собственный аналог спекулятивной реализации команд. Однако спекуляция выполняется не при работе программы, а при компиляции, что делает VLIW-процессоры устойчивыми к уязвимостям Spectre и Meltdown. Компиляторы в этой архитектуре привязаны к определённым процессорам. Так, в следующем поколении наибольшая длина одной команды может из 256 бит превратиться в 512 бит, и тогда придётся выбирать между обратной совместимостью со старым типом процессора и возрастанием производительности посредством компиляции под новый процессор. И в этом случае Open Sourсe даёт возможность получить программу под определённый процессор при помощи перекомпиляции.
Развитием указанных архитектур стали различные гибридные архитектуры. К примеру, современные x86_64 процессоры CISC-совместимы, однако имеют RISC-ядро. В этих CISC-процессорах CISC-инструкции переводятся в набор RISC-команд. Вероятно, в дальнейшем разнообразие гибридных архитектур только возрастёт.
Рис. 34. Условные команды в современных архитектурах
Выполнение по предположению (speculation)
Поддерживаемое аппаратурой выполнение по предположению позволяет выполнить команду до момента определения направления условного перехода, от которого данная команда зависит. Это снижает потери, которые возникают при наличии в программе зависимостей по управлению. Чтобы понять, почему выполнение по предположению оказывается полезным, рассмотрим следующий простой пример программного кода, который реализует проход по связанному списку и инкрементирование каждого элемента этого списка:
Подобно циклам for, с которыми мы встречались в более ранних разделах, разворачивание этого цикла не увеличит степени доступного параллелизма уровня команд. Действительно, каждая развернутая итерация будет содержать оператор if и выход из цикла. Ниже приведена последовательность команд в предположении, что значение head находится в регистре R4, который используется для хранения p, и что каждый элемент списка состоит из поля значения и следующего за ним поля указателя. Проверка размещается внизу так, что на каждой итерации цикла выполняется только один переход.
Развернув цикл однажды можно видеть, что разворачивание в данном случае не помогает:
Даже прогнозируя направление перехода мы не можем выполнять с перекрытием команды из двух разных итераций цикла, и условные команды в любом случае здесь не помогут. Имеются несколько сложных моментов для выявления параллелизма из этого развернутого цикла:
- Первая команда в итерации цикла (LW R5,0(R4)) зависит по управлению от обоих условных переходов. Таким образом, команда не может выполняться успешно (и безопасно) до тех пор, пока мы не узнаем исходы команд перехода.
- Вторая и третья команды в итерации цикла зависят по данным от первой команды цикла.
- Четвертая команда в каждой итерации цикла (LW R4,4(R4)) зависит по управлению от обоих переходов и антизависит от непосредственно предшествующей ей команды SW.
- Последняя команда итерации цикла зависит от четвертой.
Вместе эти условия означают, что мы не можем совмещать выполнение никаких команд между последовательными итерациями цикла! Имеется небольшая возможность совмещения посредством переименования регистров либо аппаратными, либо программными средствами, если цикл развернут, так что вторая загрузка более не антизависит от SW и может быть перенесена выше.
В альтернативном варианте, при выполнении по предположению, что переход не будет выполняться, мы можем попытаться совместить выполнение последовательных итераций цикла. Действительно, это в точности то, что делает компилятор с планированием трасс. Когда направление переходов может прогнозироваться во время компиляции, и компилятор может найти команды, которые он может безопасно перенести на место перед точкой перехода, решение, базирующееся на технологии компилятора, идеально. Эти два условия являются ключевыми ограничениями для выявления параллелизма уровня команд статически с помощью компилятора. Рассмотрим развернутый выше цикл. Переход просто трудно прогнозируем, поскольку частота, с которой он является выполняемым, зависит от длины списка, по которому осуществляется проход. Кроме того, мы не можем безопасно перенести команду загрузки через переход, поскольку, если содержимое R4 равно nil, то команда загрузки слова, которая использует R4 как базовый регистр, гарантированно приведет к ошибке и обычно сгенерирует исключительную ситуацию по защите. Во многих системах значение nil реализуется с помощью указателя на неиспользуемую страницу виртуальной памяти, что обеспечивает ловушку (trap) при обращении по нему. Такое решение хорошо для универсальной схемы обнаружения указателей на nil, но в данном случае это не очень помогает, поскольку мы можем регулярно генерировать эту исключительную ситуацию, и стоимость обработки исключительной ситуации плюс уничтожения результатов выполнения по предположению будет огромной.
Чтобы преодолеть эти сложности, машина может иметь в своем составе специальные аппаратные средства поддержки выполнения по предположению. Эта методика позволяет машине выполнять команду, которая может быть зависимой по управлению, и избежать любых последствий выполнения этой команды (включая исключительные ситуации), если окажется, что в действительности команда не должна выполняться. Таким образом выполнение по предположению, подобно условным командам, позволяет преодолеть два сложных момента, которые могут возникнуть при более раннем выполнении команд: возможность появления исключительной ситуации и ненужное изменение состояния машины, вызванное выполнением команды. Кроме того, механизмы выполнения по предположению позволяют выполнять команду даже до момента оценки условия командой условного перехода, что невозможно при условных командах. Конечно, аппаратная поддержка выполнения по предположению достаточно сложна и требует значительных аппаратных ресурсов.
Один из подходов, который был хорошо исследован во множестве исследовательских проектов и используется в той или иной степени в машинах, которые разработаны или находятся на стадии разработки в настоящее время, заключается в объединении аппаратных средств динамического планирования и выполнения по предположению. В определенной степени подобную работу делала и IBM 360/91, поскольку она могла использовать средства прогнозирования направления переходов для выборки команд и назначения этих команд на станции резервирования. Механизмы, допускающие выполнение по предположению, идут дальше и позволяют действительно выполнять эти команды, а также другие команды, зависящие от команд, выполняющихся по предположению. Как и для алгоритма Томасуло, поясним аппаратное выполнение по предположению на примере устройства плавающей точки, но все идеи естественно применимы и для целочисленного устройства.
Аппаратура, реализующая алгоритм Томасуло, может быть расширена для обеспечения поддержки выполнения по предположению. С этой целью необходимо отделить средства пересылки результатов команд, которые требуются для выполнения по предположению некоторой команды, от механизма действительного завершения команды. Имея такое разделение функций, мы можем допустить выполнение команды и пересылать ее результаты другим командам, не позволяя ей однако делать никакие обновления состояния машины, которые не могут быть ликвидированы, до тех пор, пока мы не узнаем, что команда должна безусловно выполниться. Использование цепей ускоренной пересылки также подобно выполнению по предположению чтения регистра, поскольку мы не знаем, обеспечивает ли команда, формирующая значение регистра-источника, корректный результат до тех пор, пока ее выполнение не станет безусловным. Если команда, выполняемая по предположению, становится безусловной, ей разрешается обновить регистровый файл или память. Этот дополнительный этап выполнения команд обычно называется стадией фиксации результатов команды (instruction commit).
Главная идея, лежащая в основе реализации выполнения по предположению, заключается в разрешении неупорядоченного выполнения команд, но в строгом соблюдении порядка фиксации результатов и предотвращением любого безвозвратного действия (например, обновления состояния или приема исключительной ситуации) до тех пор, пока результат команды не фиксируется. В простом конвейере с выдачей одиночных команд мы могли бы гарантировать, что команда фиксируется в порядке, предписанном программой, и только после проверки отсутствия исключительной ситуации, вырабатываемой этой командой, просто посредством переноса этапа записи результата в конец конвейера. Когда мы добавляем механизм выполнения по предположению, мы должны отделить процесс фиксации команды, поскольку он может произойти намного позже, чем в простом конвейере. Добавление к последовательности выполнения команды этой фазы фиксации требует некоторых изменений в последовательности действий, а также в дополнительного набора аппаратных буферов, которые хранят результаты команд, которые завершили выполнение, но результаты которых еще не зафиксированы. Этот аппаратный буфер, который можно назвать буфером переупорядочивания, используется также для передачи результатов между командами, которые могут выполняться по предположению.
Буфер переупорядочивания предоставляет дополнительные виртуальные регистры точно так же, как станции резервирования в алгоритме Томасуло расширяют набор регистров. Буфер переупорядочивания хранит результат некоторой операции в промежутке времени от момента завершения операции, связанной с этой командой, до момента фиксации результатов команды. Поэтому буфер переупорядочивания является источником операндов для команд, точно также как станции резервирования обеспечивают промежуточное хранение и передачу операндов в алгоритме Томасуло. Основная разница заключается в том, что когда в алгоритме Томасуло команда записывает свой результат, любая последующая выдаваемая команда будет выбирать этот результат из регистрового файла. При выполнении по предположению регистровый файл не обновляется до тех пор, пока команда не фиксируется (и мы знаем определенно, что команда должна выполняться); таким образом, буфер переупорядочивания поставляет операнды в интервале между завершением выполнения и фиксацией результатов команды. Буфер переупорядочивания не похож на буфер записи в алгоритме Томасуло, и в нашем примере функции буфера записи интегрированы с буфером переупорядочивания только с целью упрощения. Поскольку буфер переупорядочивания отвечает за хранение результатов до момента их записи в регистры, он также выполняет функции буфера загрузки.
Рис. 35. Расширение устройства ПТ средствами выполнения по предположению
Каждая строка в буфере переупорядочивания содержит три поля: поле типа команды, поле места назначения (результата) и поле значения. Поле типа команды определяет, является ли команда условным переходом (для которого отсутствует место назначения результата), командой записи (которая в качестве места назначения результата использует адрес памяти) или регистровой операцией (команда АЛУ или команда загрузки, в которых местом назначения результата является регистр). Поле назначения обеспечивает хранение номера регистра (для команд загрузки и АЛУ) или адрес памяти (для команд записи), в который должен быть записан результат команды. Поле значения используется для хранения результата операции до момента фиксации результата команды. На рисунке 5.35 показана аппаратная структура машины с буфером переупорядочивания. Буфер переупорядочивания полностью заменяет буфера загрузки и записи. Хотя функция переименования станций резервирования заменена буфером переупорядочивания, нам все еще необходимо некоторое место для буферизации операций (и операндов) между моментом их выдачи и началом выполнения. Эту функцию выполняют регистровые станции резервирования. Поскольку каждая команда имеет позицию в буфере переупорядочивания до тех пор, пока она не будет зафиксирована (и результаты не будут отправлены в регистровый файл), результат тегируется посредством номера строки буфера переупорядочивания, а не номером станции резервирования. Это требует, чтобы номер строки буфера переупорядочивания, присвоенный команде, отслеживался станцией резервирования.
Ниже перечислены четыре этапа выполнение команды:
- Выдача. Получает команду из очереди команд плавающей точки. Выдает команду для выполнения, если имеется свободная станция резервирования и свободная строка в буфере переупорядочивания; передает на станцию резервирования операнды, если они находятся в регистрах или в буфере переупорядочивания; и обновляет поля управления для индикации того, что буфера используются. Номер отведенной под результат строки буфера переупорядочивания также записывается в станцию резервирования, так что этот номер может использоваться для тегирования (пометки) результата, когда он помещается на CDB. Если все станции резервирования заполнены, или полон буфер переупорядочивания, выдача команды приостанавливается до тех пор, пока в обоих буферах не появится доступной строки.
- Выполнение. Если один или несколько операндов еще не готовы (отсутствуют), осуществляется просмотр CDB (Common Data Bus) и происходит ожидание вычисления значения требуемого регистра. На этом шаге выполняется проверка наличия конфликтов типа RAW. Когда оба операнда оказываются на станции резервирования, происходит вычисление результата операции.
- Запись результата. Когда результат вычислен и становится доступным, выполняется его запись на CDB (с посылкой тега буфера переупорядочивания, который был присвоен команде на этапе выдачи для выполнения) и из CDB в буфер переупорядочивания, а также в каждую станцию резервирования, ожидающую этот результат. (Можно было бы также читать результат из буфера переупорядочивания, а не из CDB, точно также, как централизованная схема управления (scoreboard) читает результаты из регистров, а не с шины завершения). Станция резервирования помечается как свободная.
- Фиксация. Когда команда достигает головы буфера переупорядочивания и ее результат присутствует в буфере, соответствующий регистр обновляется значением результата (или выполняется запись в память, если операция - запись в память), и команда изымается из буфера переупорядочивания.
Когда команда фиксируется, соответствующая строка буфера переупорядочивания очищается, а место назначения результата (регистр или ячейка памяти) обновляется. Чтобы не менять номера строк буфера переупорядочивания после фиксации результата команды, буфер переупорядочивания реализуется в виде циклической очереди, так что позиции в буфере переупорядочивания меняются, только когда команда фиксируется. Если буфер переупорядочивания полностью заполнен, выдача команд останавливается до тех пор, пока не освободится очередная строка буфера.
Поскольку никакая запись в регистры или ячейки памяти не происходит до тех пор, пока команда не фиксируется, машина может просто ликвидировать все свои выполненные по предположению действия, если обнаруживается, что направление условного перехода было спрогнозировано не верно.
Исключительные ситуации в подобной машине не воспринимаются до тех пор, пока соответствующая команда не готова к фиксации. Если выполняемая по предположению команда вызывает исключительную ситуацию, эта исключительная ситуация записывается в буфер упорядочивания. Если обнаруживается неправильный прогноз направления условного перехода и выясняется, что команда не должна была выполняться, исключительная ситуация гасится вместе с командой, когда обнуляется буфер переупорядочивания. Если же команда достигает вершины буфера переупорядочивания, то мы знаем, что она более не является выполняемой по предположению (она уже стала безусловной), и исключительная ситуация должна действительно восприниматься.
Эту методику выполнения по предположению легко распространить и на целочисленные регистры и функциональные устройства. Действительно, выполнение по предположению может быть более полезно в целочисленных программах, поскольку именно такие программы имеют менее предсказуемое поведение переходов. Кроме того, эти методы могут быть расширены так, чтобы обеспечить работу в машинах с выдачей на выполнение и фиксацией результатов нескольких команд в каждом такте. Выполнение по предположению возможно является наиболее интересным методом именно для таких машин, поскольку менее амбициозные машины могут довольствоваться параллелизмом уровня команд внутри базовых блоков при соответствующей поддержке со стороны компилятора, использующего технологию разворачивания циклов.
Очевидно, все рассмотренные ранее методы не могут достичь большей степени распараллеливания, чем заложено в конкретной прикладной программе. Вопрос увеличения степени параллелизма прикладных систем в настоящее время является предметом интенсивных исследований, проводимых во всем мире.
Читайте также: