Конвейер: как ускорить без роста частоты
Урок объясняет конвейеризацию — приём, позволяющий выполнять несколько команд одновременно на разных стадиях.
Конвейер (pipeline) — разбиение исполнения команды на ступени так, что разные команды одновременно находятся на разных ступенях, как изделия на сборочной линии.
Аналогия с прачечной
Представьте стирку: постирать (30 мин), высушить (30 мин), сложить (30 мин). Если делать четыре загрузки по очереди, целиком завершая каждую, уйдёт 4×90 = 360 минут. Но как только первая загрузка ушла в сушку, можно ставить вторую в стирку! Стиралка, сушилка и стол работают параллельно. Так четыре загрузки займут не 360, а 180 минут. Это и есть конвейер: ступени всегда заняты.
Пять классических ступеней RISC
IF - Instruction Fetch (выборка команды)
ID - Instruction Decode (декодирование + чтение регистров)
EX - Execute (АЛУ-операция)
MEM - Memory access (доступ к памяти)
WB - Write Back (запись результата)
Без конвейера (по очереди):
i1: IF ID EX MEM WB
i2: IF ID EX MEM WB
i3: IF ID EX MEM WB
С конвейером (перекрытие):
такт: 1 2 3 4 5 6 7
i1: IF ID EX MEM WB
i2: IF ID EX MEM WB
i3: IF ID EX MEM WB
каждый такт ЗАВЕРШАЕТСЯ одна команда!
Как работает под капотом: ускорение
В идеале конвейер из k ступеней даёт ускорение почти в k раз: после заполнения каждая ступень завершает по одной команде за такт. Но есть «разогрев» (заполнение конвейера) и «слив» в конце. Посчитаем точно:
def cycles_no_pipeline(n_instr, stages):
return n_instr * stages
def cycles_pipeline(n_instr, stages):
# первая команда проходит все ступени, далее +1 такт на команду
return stages + (n_instr - 1)
for n in (1, 5, 100, 1000):
seq = cycles_no_pipeline(n, 5)
pipe = cycles_pipeline(n, 5)
speedup = seq / pipe
print(f"{n:>4} команд: без конв.={seq:>5} тактов | конв.={pipe:>5} | ускорение x{speedup:.2f}")Вывод:
1 команд: без конв.= 5 тактов | конв.= 5 | ускорение x1.00 5 команд: без конв.= 25 тактов | конв.= 9 | ускорение x2.78 100 команд: без конв.= 500 тактов | конв.= 104 | ускорение x4.81 1000 команд: без конв.= 5000 тактов | конв.= 1004 | ускорение x4.98
Видно: чем длиннее поток команд, тем ближе ускорение к числу ступеней (5). На большом потоке оно почти ×5 — конвейер «разогрелся» и работает на полную.
Пропускная способность против задержки
Конвейер не ускоряет одну команду (она по-прежнему проходит 5 ступеней). Он повышает пропускную способность (throughput) — сколько команд завершается в секунду. Это как конвейер на заводе: одна машина собирается столько же времени, но машин в час выходит куда больше.
Глубокие конвейеры
Можно дробить мельче: 10, 15, 20 ступеней. Тогда каждая ступень короче, значит такт можно сделать быстрее (выше частота). Но платой становятся бо́льшие потери при сбросе конвейера (см. конфликты переходов). Поэтому глубину выбирают как компромисс.
Глубже в тему
Почему конвейеризация оказалась таким революционным приёмом? Потому что она повышает производительность, почти не трогая физику. Можно было бы ускорять процессор, поднимая тактовую частоту, но частота упирается в тепло и пределы материалов. Конвейер же действует иначе: он не делает отдельную команду быстрее, а заставляет аппаратуру, которая раньше простаивала, всегда быть занятой. Пока АЛУ считает одну команду, блок выборки уже тащит следующую, а блок записи фиксирует результат позапрошлой. Это пример фундаментального инженерного принципа — повышать загрузку имеющихся ресурсов вместо наращивания их мощности. Аналогия с прачечной точна именно потому, что подчёркивает: ключ не в более быстрой стиралке, а в том, чтобы стиралка, сушилка и стол не простаивали.
Важно глубже понять разницу между задержкой (latency) и пропускной способностью (throughput), потому что их путаница порождает неверные ожидания. Задержка — это время от старта до финиша одной команды; она в конвейере не уменьшается, а иногда даже растёт (добавляются регистры-защёлки между ступенями, на которые тратится время). Пропускная способность — это сколько команд завершается в единицу времени; именно её конвейер кардинально повышает. Для пользователя важна обычно пропускная способность: программа состоит из миллионов команд, и нам всё равно, что каждая отдельная проходит пять ступеней, — важно, что в установившемся режиме каждый такт одна команда сходит с конвейера. Это та же логика, по которой завод меряют машинами в час, а не временем сборки одного автомобиля.
Идеальное ускорение, равное числу ступеней, — это потолок, к которому реальность лишь приближается. Мешают три фактора. Во-первых, разогрев и слив: пока конвейер заполняется первыми командами и опустошается последними, он работает не на полную, поэтому на коротких потоках выигрыш скромный (что хорошо видно в расчёте из урока: на 5 командах всего ×2,78). Во-вторых, ступени почти никогда не равны по длительности, а такт приходится подгонять под самую медленную ступень — остальные простаивают часть такта. В-третьих, конфликты (hazards), которым посвящён следующий урок, заставляют конвейер останавливаться. Реальное ускорение поэтому всегда меньше теоретического, и инженеры борются за каждую десятую долю.
Выбор глубины конвейера — это отдельная драматичная история архитектуры. Чем больше ступеней, тем короче каждая, а значит, можно поднять частоту. Pentium 4 от Intel пошёл по пути сверхглубокого конвейера (более 20, а в поздних версиях около 31 ступени), рассчитывая выиграть гонку гигагерц. Но платой стала чудовищная цена ошибки предсказания перехода: при сбросе терялись десятки уже начатых команд, и реальная производительность на ветвистом коде разочаровывала. Это привело к развороту индустрии: последующие архитектуры (Intel Core) вернулись к умеренной глубине (около 14 ступеней) и стали брать своё шириной (суперскалярностью) и умом (лучшими предсказателями), а не голой частотой. Урок истории: глубина конвейера — это компромисс, а не самоцель, и «больше ступеней» далеко не всегда означает «быстрее».
Частые ошибки
- Думать, что конвейер ускоряет одну команду. Задержка одной команды та же; растёт пропускная способность потока.
- Считать ускорение всегда равным числу ступеней. Мешают разогрев, конфликты и переходы; реальное ускорение меньше идеального.
- Игнорировать цену глубины. Глубокий конвейер быстрее по частоте, но дороже при сбросах.
Итог
- Конвейер перекрывает команды по ступеням (IF/ID/EX/MEM/WB) — как сборочная линия.
- Ускорение стремится к числу ступеней на длинном потоке команд.
- Растёт пропускная способность, а не скорость отдельной команды.