Давайте разберемся в основах архитектур процессоров. Я работаю в сфере ИКТ, но моя специализация — не процессоры, поэтому этот обзор будет затрагивать самые базовые понятия.
Вы наверняка слышали про отечественный процессор «Эльбрус»? А возможно, слышали и мои нелестные отзывы о нем. Они основаны на том, что «Эльбрус» (ExpLicit Basic Resources Utilization Scheduling — «явное планирование использования основных ресурсов») использует архитектуру VLIW, которая плохо подходит для процессоров общего назначения, хотя и имеет определенные перспективы. Но обо всем по порядку.
Эпоха CISC: Сложные команды и хитрый конвейер
Начнем с архитектуры, которая до недавнего времени была доминирующей — CISC (Complex Instruction Set Computing). Яркий пример — процессоры Intel x86 (IA-32).
Как следует из названия, такая архитектура использует большой и сложный набор команд, способных выполнять практически любые операции. Длина команд при этом разная. Это логично: команда сброса регистра на стек короткая (ей нужен только код операции и номер регистра), а команда сложения регистра с ячейкой памяти — длинная (код операции, номер регистра и 32-битный адрес).
Чтобы такие команды выполнялись быстро, нужен конвейер (pipeline). Принцип похож на сборочный конвейер: каждый участок выполняет свою небольшую операцию, что в итоге ускоряет процесс.
На примере нашей длинной команды: процессору нужно не просто выполнить сложение, а сначала загрузить данные из памяти, что отнимает время. Чтобы конвейер не простаивал, используют хитрый трюк с «фантомными» регистрами. Данные заранее, в фоновом режиме, подгружаются в эти невидимые регистры. Когда команда поступает на выполнение, процессор просто подменяет их на «настоящие» и мгновенно производит операцию. Выигрыш в скорости огромен, особенно если адрес памяти еще нужно вычислить.
Но есть проблема: ветвления. Если в программе встречается условный переход, конвейер, забитый «неправильными» командами, приходится полностью очищать и загружать заново. Это ведет к простоям. Поэтому критически важен предсказатель ветвлений — блок, который пытается угадать, какая ветка кода будет выполняться следующей. От его точности сильно зависит общая производительность.
Замечу, что современные CISC-процессоры — не совсем «чистые». Сложные команды внутри них часто превращаются в серию простых микроинструкций (микрокод). То есть, большая команда — это, по сути, макрос.
RISC: Простота — залог скорости
Логичным развитием идеи стала архитектура RISC (Reduced Instruction Set Computer).
Ее идея проста: давайте уберем все сложные команды и сделаем все инструкции одинаковой, фиксированной длины. Вместо одной команды «сложить регистр с памятью» будут две: «загрузить данные из памяти в регистр» и «сложить два регистра».
Это дает огромные преимущества:
- Упрощается декодирование команд. Процессору не нужно гадать, какого размера следующая инструкция.
- Упрощается сам процессор. Это позволяет взвинчивать тактовые частоты и делать более сбалансированный и глубокий конвейер.
Фактически, современные Intel и AMD — это RISC-процессоры внутри. CISC-совместимость сохранена на уровне слоя микрокода, который превращает сложные команды в набор простых RISC-операций.
Кстати, о классическом RISC. Ярчайшим примером его философии является архитектура MIPS. Её создатели пошли ещё дальше и попытались полностью убрать аппаратные механизмы разрешения конфликтов в конвейере (блокировки), переложив эту задачу на компилятор.
Тот должен был так менять порядок инструкции и вставлять пустые операции, чтобы конвейер никогда не простаивал из-за зависимостей данных. Хотя в чистом виде эта идея не выжила, она прекрасно иллюстрирует главный принцип: RISC максимально упрощает аппаратное обеспечение, возлагая всю интеллектуальную работу по оптимизации на компилятор. Это прямой предшественник идеи VLIW, где роль компилятора становится абсолютно критической.
VLIW: за все отвечает компилятор
И, наконец, мы добрались до VLIW (Very Long Instruction Word), которую использует «Эльбрус».
Идея еще радикальнее: одна очень длинная инструкция содержит в себе несколько простых операций (скажем, 2 арифметические, 1 загрузку и 1 сохранение), которые процессор должен выполнить параллельно за один такт. Можно сказать, это «вывернутый наружу» микрокод, который программа подает процессору на непосредственное выполнение.
С одной стороны, это гениально и позволяет избавиться от самых сложных и энергоемких блоков процессора: от аппаратного планировщика команд, предсказателя ветвлений и прочих ухищрений для параллелизации. Процессор становится проще, а значит, дешевле и энергоэффективнее.
Но есть две огромные проблемы:
- Требуется огромное количество регистров для хранения промежуточных результатов всех этих параллельных операций.
- Вся ответственность за параллелизм ложится на компилятор. Именно компилятор должен заранее, статически, распланировать, какие операции можно выполнить параллельно, и упаковать их в одну длинную инструкцию.
Это создает титаническую задачу для разработчиков компиляторов. Даже с самыми лучшими компиляторами равномерно и полностью загрузить все исполнительные устройства процессора удается редко. Часть «слотов» в длинной инструкции простаивает, что приводит к низкой плотности кода и падению производительности на сложном, плохо предсказуемом коде (например, в приложениях общего назначения). Эту проблему называют проблемой низкой плотности кода.
крометого, такая архитектура создаёт жёсткую зависимость от компилятора и потеря обратной совместимости. Код, идеально скомпилированный и упакованный для одного конкретного VLIW-процессора (скажем, с 2 integer ALU, 1 FPU и 1 блоком загрузки), будет крайне неэффективно выполняться на процессоре с другой конфигурацией исполняющих устройств (например, с 3 integer ALU и 2 FPU). Он не сможет автоматически использовать новые ресурсы.
Кроме того проблема ветвления (if, else, switch): Компилятор планирует выполнение программы статически (до запуска), в то время как реальное выполнение программы — динамично и непредсказуемо. Компилятор не знает, какая ветка кода будет выполняться. это как правило определяется входными данными - пришёл 0 переход не сработал пришло что-то отличное от нуля переход срабатывает. Компилятор вынужден планировать обе ветки. Это может приводить к «пузырям» в конвейере, если предсказание, заложенное в код, окажется неверным.
Так есть ли будущее у VLIW? Возможно, и вот оно
Потенциальный ренессанс архитектуры VLIW может быть связан с искусственным интеллектом.
ИИ идеально подходит для решения такой задачи: он может проанализировать гигантские объемы кода, провести глубокий статистический анализ и найти идеальный способ распараллеливания и упаковки инструкций для конкретной программы. Я не знаю, ведутся ли такие эксперименты на практике, но идея выглядит весьма перспективной, хотя всех проблем она не снимет.
Вывод: время «чистого» CISC прошло, его место занял гибридный подход (RISC-ядро + CISC-оболочка). А VLIW пока находит свою нишу в специализированных задачах (например, цифровая обработка сигналов — DSP), где алгоритмы хорошо предсказуемы и параллелизуемы. Для процессоров общего назначения, каким является «Эльбрус», эта архитектура сегодня малоэффективна, но в будущем, с приходом AI-компиляторов, все может измениться.
написано в соавторстве с deepseec.