RISC против CISC

Урок сравнивает два подхода к проектированию набора команд, которые определили облик всех современных процессоров.

CISC (Complex Instruction Set) — много сложных команд, каждая делает многое. RISC (Reduced Instruction Set) — мало простых команд одинаковой длины, исполняемых за один такт.

Откуда взялся спор

В эпоху дорогой памяти выгодны были «мощные» команды: одна команда CISC могла, например, скопировать строку или сделать сложение прямо с памятью. Программа выходила короче. Но в 1980-х выяснилось: сложные команды трудно делать быстрыми, а компиляторы всё равно используют лишь малую их часть. Родилась идея RISC: набор из простых, однотактных команд, которые легко конвейеризовать.

Главные отличия

СвойствоCISC (x86)RISC (ARM, RISC-V)
Число командсотни, сложныенемного, простые
Длина командыпеременная (1–15 байт)фиксированная (обычно 4 байта)
Доступ к памятипочти любая командатолько LOAD/STORE
Тактов на командупеременноев идеале один
Сложность декодеравысокаянизкая
Длина кодакорочедлиннее

Архитектура load/store

Ключевая черта RISC — модель load/store: арифметика работает только с регистрами, а с памятью общаются лишь две команды — LOAD (загрузить из памяти в регистр) и STORE (выгрузить из регистра в память). Это упрощает конвейер. Сравним, как одно «прибавить к ячейке памяти» выглядит в двух мирах:

; CISC (x86): одна команда сразу работает с памятью
ADD [x], 5          ; память[x] = память[x] + 5

; RISC: три простые команды (load -> вычисли -> store)
LOAD  R1, [x]       ; R1 = память[x]
ADDI  R1, R1, 5     ; R1 = R1 + 5
STORE [x], R1       ; память[x] = R1

Как работает под капотом: современный гибрид

Сегодня граница размылась. Процессоры x86 (CISC снаружи) внутри разбивают сложные команды на простые микрооперации (micro-ops) — то есть исполняют их по-RISC-овски. А RISC-архитектуры (ARM) обросли расширениями. Промоделируем «число тактов» и «размер кода» как компромисс двух философий:

# модель: команда 'прибавить к памяти'
cisc = {"команд": 1, "байт_на_команду": 6, "тактов": 4}
risc = {"команд": 3, "байт_на_команду": 4, "тактов": 1}  # на команду

for name, m in (("CISC", cisc), ("RISC", risc)):
    total_bytes = m["команд"] * m["байт_на_команду"]
    total_cycles = m["команд"] * m["тактов"]
    print(f"{name}: команд={m['команд']}, байт={total_bytes}, тактов={total_cycles}")

Вывод:

CISC: команд=1, байт=6, тактов=4
RISC: команд=3, байт=12, тактов=3

Видно компромисс: CISC компактнее по коду, RISC — короче по тактам и проще для конвейера. Поэтому энергоэффективные устройства (телефоны) тяготеют к RISC (ARM), а десктопы исторически — к x86, но грань стирается.

Глубже в тему

Почему спор RISC и CISC вообще разгорелся именно в начале 1980-х, а не раньше? Дело в экономике памяти. Когда ОЗУ стоило дорого и измерялось килобайтами, каждый сэкономленный байт кода был на вес золота, и «толстые» команды CISC, упаковывающие много работы в пару байт, выглядели разумным инженерным выбором. Но к 1980-м память подешевела, зато проявилось другое узкое место — скорость декодирования и исполнения. Команда VAX, способная за один опкод обойти массив и вычислить полином, требовала микрокода в десятки тактов, и конвейеризовать такое чудовище было почти невозможно. Исследования Дэвида Паттерсона в Беркли и Джона Хеннесси в Стэнфорде показали статистику: компиляторы реально использовали лишь малую долю богатого набора команд, а сложные адресации простаивали. Отсюда родилась гипотеза RISC — упростить набор так, чтобы каждую команду можно было сделать однотактной и легко конвейеризуемой.

Важный нюанс, который часто упускают: RISC выигрывает не потому, что «команд меньше», а потому, что они регулярны. Фиксированная длина в 4 байта означает, что декодер всегда знает, где кончается одна команда и начинается следующая, — а значит, можно выбирать сразу несколько команд за такт и декодировать их параллельно. У x86 длина команды плавает от 1 до 15 байт, и чтобы найти границу следующей команды, декодеру приходится частично разбирать предыдущую. Это создаёт последовательную зависимость в самом начале конвейера. Современные процессоры Intel и AMD борются с этим, тратя заметную часть кристалла на сложные параллельные декодеры и кэш уже декодированных micro-ops (uop cache), чтобы не платить за разбор переменной длины повторно в горячих циклах.

Любопытно, что «победа» одной философии так и не наступила — вместо этого произошла конвергенция. Внутри современного x86-ядра сложная команда вроде ADD [mem], reg разбивается на цепочку micro-ops, которые исполняются ровно по RISC-принципам: загрузка, вычисление, запись. С другой стороны, ARM, начинавшийся как чистый RISC, оброс расширениями (NEON для SIMD, аппаратное деление, криптоинструкции), и его набор команд уже не назовёшь «сокращённым». Сегодня архитектура — это в первую очередь вопрос совместимости и экосистемы, а не теоретической чистоты. x86 доминирует на десктопах и серверах из-за гигантского наследия программ, а ARM захватил мобильные устройства благодаря энергоэффективности и удачной бизнес-модели лицензирования.

Стоит отметить и третьего игрока — RISC-V, открытую ISA без лицензионных отчислений. Она интересна тем, что спроектирована заново с учётом всех уроков сорока лет: модульная (базовый набор плюс опциональные расширения), без «исторического балласта» вроде слотов задержки перехода, которые в MIPS казались хорошей идеей, а потом мешали. RISC-V показывает, что спор RISC и CISC давно перешёл из плоскости «сколько команд» в плоскость «как организовать набор так, чтобы он одинаково хорошо работал и в крошечном микроконтроллере, и в суперкомпьютере». Для инженера практический вывод прост: понимание этих компромиссов помогает читать ассемблер любой платформы и осознавать, почему один и тот же алгоритм по-разному ложится на разное железо.

Частые ошибки

  • Думать, что RISC «слабее». «Reduced» — про простоту команд, а не про мощность процессора; быстрейшие чипы бывают RISC.
  • Считать, что x86 исполняет команды «как есть». Внутри он переводит их в RISC-подобные микрооперации.
  • Игнорировать длину команды. Фиксированная длина RISC сильно упрощает выборку и декодирование.

Итог

  • CISC: много сложных команд переменной длины, работающих с памятью напрямую.
  • RISC: мало простых однотактных команд фиксированной длины, модель load/store.
  • Современные CPU — гибрид: CISC-фасад с RISC-микрооперациями внутри.
Проверьте себя
1. Что характерно для архitектуры load/store (RISC)?
AЛюбая команда может обращаться к памяти
BС памятью работают только команды LOAD и STORE, арифметика — лишь с регистрами
CПамять не используется вообще
DКоманды имеют переменную длину
2. Что означает «Reduced» в RISC?
AПроцессор медленнее
BНабор команд проще: команды простые, короткие, обычно однотактные
CМеньше памяти
DМеньше регистров
3. Как современные x86-процессоры сочетают CISC и RISC?
AОни полностью отказались от CISC
BСнаружи это CISC, но внутри сложные команды разбиваются на простые микрооперации
CОни исполняют только RISC-команды
DНикак не сочетают