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-микрооперациями внутри.