В погоне за гигагерцами

В погоне за гигагерцами

Как известно, на сегодняшний день существует два принципиальных направления повышения производительности процессоров, которые в разных соотношениях пытаются сочетать разработчики: увеличение скорости исполнения команд (реализующееся за счет уменьшения размеров электронных компонент схем, повышения тактовой частоты) и использование новых архитектур процессоров. Первый путь требует новые технологические решения (переход на технологии 0.13 и 0.09 микрон, новые материалы для создания кристаллов), второй же проще, потому что опирается на схемотехнику и усовершенствование программных методов. Поэтому сейчас усилия разработчиков в гораздо большей, чем раньше, степени сосредоточены именно на использовании всех возможностей микроархитектуры.

Технические описания современных высокопроизводительных процессоров гордо повествуют о дальнейшем развитии в их внутренней архитектуре принципов суперскалярности, конвейерной обработки, оптимизации последовательности выполнения команд, упреждающего исполнения, предсказания ветвлений и переходов. Что же стоит за этими внушающими благоговейный трепет и уважение словами?
Суперскалярная архитектура подразумевает одновременное выполнение нескольких команд в параллельно работающих исполнительных устройствах. Например, происходит параллельная работа арифметико-логических блоков, проверка условий ветвления для команд условного перехода. При этом исполнительное ядро работает с повышенной скоростью выполнения операций (Intel называет это Rapid Execute Engineр — Устройство Быстрого Исполнения). Проблемы появляются, когда, например, одна команда должна получить на вход результат выполнения другой. Для ускорения работы и в таких случаях поступают двояко. Реализуют явную либо прозрачную суперскалярность. В первом случае процессор тупо выполняет несколько команд за такт, просто игнорируя возможное влияние выполнения предшествующей по ходу выполнения команды на аргументы последующей, ну, а учитывается это уже на человеческом уровне — программистом, пишущим под процессор (если не учесть, то, понятно, ничего хорошего на выходе не получится). Во втором случае процессор сам учитывает это влияние и блокирует выполнение команд, аргументы которых еще не вычислены, а игнорирование суперскалярности процессора программистом приведет не к неправильному выполнению программы, а лишь к потере эффективности — страховочка, так сказать... От нерадивых программистов:).
Тесно связана с суперскалярностью оптимизация выполнения команд, которая также реализуется на аппаратном уровне. Она обеспечивает выполнение команд в порядке, исключающем их зависимость друг от друга по данным. Процессор загружает в специальный буфер небольшую последовательность микрокоманд, а затем разбирается, какие из них лучше выполнить в данный момент (пусть даже не в порядке следования) с учетом сложившейся ситуации — например, произвести арифметические действия или заранее запросить данные из памяти, чтобы потом, когда они будут нужны, не тормозить:). Полученные результаты сохраняются в специальных буферных регистрах процессора, а затем восстанавливается нормальный порядок выполнения микрокоманд.

Конвейерная обработка подразумевает работу с поступающими командами по принципу конвейера (простите за тавтологию:)) — следующая команда загружается в процессор и начинает выполняться не дожидаясь окончания выполнения предыдущих. В новейших процессорах — не просто конвейерная, а гиперконвейерная (!) обработка с 20 ступенями (у Pentium 4 — сравните: у Pentium III 11 ступеней конвейера). Таким образом, одновременно в процессе выполнения может находиться до 20 команд, находящихся на разных стадиях их реализации. Благодаря разбиению выполнения каждой команды на более мелкие этапы (этапы подготовки к исполнению, переупорядоченного исполнения и последовательного завершения, в каждом из них также несколько ступеней) исполнение каждого из них аппаратно ускорено (разумеется, легче воплотить в "железе" реализацию простых микрокоманд, чем команд со сложной логикой). Это позволяет практически беспрепятственно увеличивать частоту процессора.
Но не все так безоблачно. Камнем преткновения являются условные ветвления, ведь процессор не может знать, будет переход выполнен или нет, до тех пор, пока команда не пройдет исполнительную ступень конвейера. Для ускорения определения направления условного перехода применяется так называемое предсказание ветвлений. Процессор определяет наиболее вероятное направление перехода либо статически (по заранее заданным правилам), либо динамически — по собираемой им статистике выполнения этого ветвления за прошедшее время (так как часто переход выполняется более одного раза). А в некоторых моделях (чаще всего с программным распараллеливанием — о нем я упомяну ниже) и программисту оставлена возможность "намекнуть", куда переход более вероятен! Декларируемая вероятность правильного определения переходов в современных процессорах — 90-95%. Предсказанная ветвь сразу направляется на конвейер для выполнения — это и есть упреждающее выполнение переходов — и если предсказание "сбылось", то переход выполняется за один такт. Но если процессор ошибся, то приходится очищать весь длиннющий конвейер и заполнять его уже командами из "правильной" ветви. Здесь и теряется драгоценное время.
Один из напрашивающихся путей решения проблемы — посылать вторую ветвь условного перехода на дополнительный конвейер параллельно с первой и после проверки условия ветвления дальше идти лишь в правильном направлении, отменив выполнение другой ветви. Ну, а если и в ветвях есть свои условные ветвления? Нелегкая задачка... И уж совсем плохо обстоит дело с аппаратными прерываниями, которые обрабатываются слишком медленно, и весь эффект от конвейеризации сводится на нет.

Также следует отметить, что намечается явная тенденция к программному распараллеливанию вычислений, то есть нагрузка в значительной части переносится с "железа" на компилятор (так называемая EPIC — "концепция реализации параллелизма на уровне компилятора"). Согласно этой концепции строятся архитектуры, принципы которых, во-первых, позволяют во время исполнения отказаться от проверки зависимостей между операциями, которые компилятор уже объявил как независимые, а во-вторых — отказаться от сложной логики внеочередного исполнения операций, полагаясь на порядок выдачи команд, определенный компилятором. В отличие от программ для суперскалярных процессоров, коды для таких архитектур предлагают точный план того, как процессор будет выполнять программу. Код совершенно определенно указывает, когда будет выполнена каждая операция, какие функциональные устройства будут работать и какие регистры будут содержать операнды. За вероятностное выполнение условных переходов также отвечает компилятор, указывая процессору наиболее вероятную, по его мнению, ветвь для упреждающего исполнения переходов. Эти и некоторые другие принципы функционирования таких архитектур позволяют создавать относительно простые по схемотехнике процессоры, которые работают быстрее своих суперскалярных аналогов (но только на определенных классах задач!). По мере внедрения таких архитектур, по-видимому, потеряет смысл сравнение производительности процессоров по их тактовой частоте, на первый план здесь выйдет эффективность компилятора.
Обратной стороной медали тут является, во-первых, привязка такого компилятора к конкретному типу процессора, а во-вторых — фактически статическая оптимизация программного кода, практически не дающая повышения производительности для объектно-ориентированных приложений.
Наилучший эффект дает сочетание в архитектуре двух этих концепций — суперскалярной (распараллеливание на аппаратном уровне) и EPIC (распараллеливание на программном уровне). Поэтому и все усилия разработчиков сейчас направлены на то, чтобы оптимально соотнести эти подходы.

Александр Купреев, HonorHim@mail.ru


Компьютерная газета. Статья была опубликована в номере 23 за 2003 год в рубрике hard :: технологии

©1997-2024 Компьютерная газета