Если регистры такие невероятно быстрые, почему у нас их не стало больше?

88

В 32-битной версии у нас было 8 регистров общего назначения. С 64-битной версией количество удваивается, но, кажется, не зависит от самого 64-битного изменения.
Теперь, если регистры такие быстрые (нет доступа к памяти), почему их, естественно, не стало больше? Разве сборщики ЦП не должны загружать в ЦП как можно больше регистров? Каково логическое ограничение того, почему у нас есть только та сумма, которая у нас есть?

Xeo
источник
Процессоры и графические процессоры скрывают задержку в основном за счет кеширования и массивной многопоточности соответственно. Итак, процессоры имеют (или нуждаются) несколько регистров, тогда как графические процессоры имеют десятки тысяч регистров. См. Мой обзорный документ о файле реестра GPU, в котором обсуждаются все эти компромиссы и факторы.
user984260

Ответы:

119

Есть много причин, по которым у вас не просто огромное количество регистров:

  • Они тесно связаны с большинством стадий конвейера. Для начала вам необходимо отслеживать их время жизни и возвращать результаты на предыдущие этапы. Сложность становится непреодолимой очень быстро, и количество задействованных проводов (буквально) растет с той же скоростью. Это дорого по площади, что в конечном итоге означает, что после определенного момента он станет дорогим по мощности, цене и производительности.
  • Он занимает место для кодирования инструкций. 16 регистров занимают 4 бита для источника и назначения и еще 4, если у вас есть инструкции с 3 операндами (например, ARM). Ужасно много места для кодирования набора инструкций занято только для указания регистра. В конечном итоге это влияет на декодирование, размер кода и, опять же, на сложность.
  • Есть лучшие способы добиться того же результата ...

В наши дни у нас действительно много регистров - просто они явно не запрограммированы. У нас есть «переименование реестра». Хотя вы получаете доступ только к небольшому набору (8-32 регистров), на самом деле они поддерживаются гораздо большим набором (например, 64-256). Затем ЦП отслеживает видимость каждого регистра и размещает их в переименованном наборе. Например, вы можете загружать, изменять, а затем сохранять в регистре много раз подряд, и каждая из этих операций фактически выполняется независимо в зависимости от промахов в кеше и т. Д. В ARM:

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Ядра Cortex A9 действительно переименовывают регистры, поэтому первая загрузка в «r0» фактически идет в переименованный виртуальный регистр - назовем его «v0». Загрузка, приращение и сохранение происходят на «v0». Между тем, мы также снова выполняем загрузку / изменение / сохранение в r0, но оно будет переименовано в «v1», потому что это полностью независимая последовательность с использованием r0. Допустим, загрузка с указателя в «r4» остановилась из-за промаха кеша. Ничего страшного - нам не нужно ждать, пока "r0" будет готов. Поскольку он переименован, мы можем запустить следующую последовательность с «v1» (также сопоставленным с r0) - и, возможно, это попадание в кеш, и мы только что получили огромный выигрыш в производительности.

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

Я думаю, что в наши дни x86 имеет гигантское количество переименованных регистров (приблизительное 256). Это означало бы иметь 8 бит умножить на 2 для каждой инструкции, чтобы просто сказать, что такое источник и место назначения. Это значительно увеличило бы количество проводов, необходимых для прохождения сердечника, и его размер. Таким образом, существует золотая середина между 16-32 регистрами, на которую согласились большинство разработчиков, и для нестандартных схем ЦП переименование регистров - способ смягчить ее.

Изменить : важность выполнения вне очереди и переименования реестра. Если у вас есть ООО, количество регистров не имеет большого значения, потому что они просто «временные теги» и переименовываются в гораздо больший набор виртуальных регистров. Вы не хотите, чтобы это число было слишком маленьким, потому что становится трудно писать небольшие последовательности кода. Это проблема для x86-32, потому что ограниченные 8 регистров означают, что множество временных файлов в конечном итоге проходит через стек, а ядру требуется дополнительная логика для пересылки операций чтения / записи в память. Если у вас нет ООО, вы обычно говорите о небольшом ядре, и в этом случае большой набор регистров дает низкое преимущество в соотношении цена / производительность.

Таким образом, существует естественная золотая середина для размера банка регистров, который составляет максимум около 32 регистров для большинства классов ЦП. x86-32 имеет 8 регистров и определенно слишком мал. В ARM было 16 регистров, и это хороший компромисс. 32 регистра - это немного многовато - вам не понадобятся последние 10 или около того.

Ничего из этого не касается дополнительных регистров, которые вы получаете для SSE и других векторных сопроцессоров с плавающей запятой. Это имеет смысл как дополнительный набор, потому что они работают независимо от целочисленного ядра и не увеличивают сложность процессора экспоненциально.

Джон Рипли
источник
12
Отличный ответ - я бы хотел добавить еще одну причину - чем больше у вас регистров, тем больше времени требуется, чтобы перебросить их / вытащить их из стека при переключении контекста. Определенно не главный вопрос, но соображение.
Will A
7
@Will Хорошее замечание. Однако в архитектуре с большим количеством регистров есть способы снизить эту стоимость. ABI обычно имеет функцию сохранения вызываемого объекта для большинства регистров, поэтому вам нужно сохранить только базовый набор. Переключение контекста обычно достаточно дорогое, поэтому дополнительное сохранение / восстановление не стоит больших затрат по сравнению со всей другой бюрократизмом. SPARC на самом деле работает над этим, делая банк регистров «окном» в области памяти, так что он в некоторой степени масштабируется с этим (вроде как махнул рукой).
Джон Рипли
4
Считайте, что мой ум был поражен таким обстоятельным ответом, которого я точно не ожидал. Кроме того, спасибо за это объяснение того, почему нам действительно не нужно столько именованных регистров, это очень интересно! Мне очень понравилось читать ваш ответ, потому что я полностью заинтересован в том, что происходит «под капотом». :) Я подожду еще немного, прежде чем принять ответ, потому что никогда не знаешь, но мой +1 уверен.
Xeo
1
Независимо от того, где лежит ответственность за сохранение регистров, время, необходимое для этого, является административным. ОК, переключение контекста может быть не самым частым случаем, но прерывания - это так. Запрограммированные вручную подпрограммы могут сэкономить на регистрах, но если драйверы написаны на C, есть вероятность, что функция, объявленная прерыванием, сохранит каждый отдельный регистр, вызовет isr и затем восстановит все сохраненные регистры. IA-32 имел преимущество прерывания с его 15-20 регистрами по сравнению с 32+ регистрами архитектур RISC.
Olof Forshell
1
Отличный ответ, но я не соглашусь с прямым сравнением "переименованных" регистров с "реальными" адресными. На x86-32 даже с 256 внутренними регистрами вы не можете использовать более 8 временных значений, хранящихся в регистрах, в любой отдельной точке выполнения. По сути, переименование регистров - это только любопытный побочный продукт OOE, не более того.
noop
12

Мы ли их больше

Поскольку почти каждая инструкция должна выбирать 1, 2 или 3 архитектурно видимых регистра, увеличение их количества приведет к увеличению размера кода на несколько бит для каждой инструкции и, таким образом, к снижению плотности кода. Это также увеличивает объем контекста, который должен быть сохранен как состояние потока и частично сохранен в записи активации функции . Эти операции происходят часто. Блокировки конвейера должны проверять табло для каждого регистра, и это имеет квадратичную временную и пространственную сложность. И, возможно, самая большая причина - просто совместимость с уже определенным набором инструкций.

Но оказывается, благодаря зарегистрировать переименование , мы действительно имеем много доступных регистров, и мы даже не нужно , чтобы спасти их. На самом деле у ЦП много наборов регистров, и он автоматически переключается между ними по мере выполнения вашего кода. Это делается исключительно для того, чтобы получить больше регистров.

Пример:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

В архитектуре, которая имеет только r0-r7, следующий код может быть автоматически переписан ЦП как что-то вроде:

load  r1, a
store r1, x
load  r10, b
store r10, y

В этом случае r10 - это скрытый регистр, который временно заменяет r1. ЦП может сказать, что значение r1 больше не используется после первого сохранения. Это позволяет отложить первую загрузку (даже попадание в кеш-память на кристалле обычно занимает несколько циклов), не требуя задержки второй загрузки или второго хранилища.

DigitalRoss
источник
2

Они постоянно добавляют регистры, но часто привязаны к инструкциям специального назначения (например, SIMD, SSE2 и т. Д.) Или требуют компиляции под конкретную архитектуру ЦП, что снижает переносимость. Существующие инструкции часто работают с конкретными регистрами и не могли бы использовать преимущества других регистров, если бы они были доступны. Старый набор инструкций и все такое.

Сет Робертсон
источник
1

Чтобы добавить сюда немного интересной информации, вы заметите, что наличие 8 регистров одинакового размера позволяет кодам операций поддерживать согласованность с шестнадцатеричной нотацией. Например, инструкция push axимеет код операции 0x50 на x86 и увеличивается до 0x57 для последнего регистра di. Затем инструкция pop axначинается с 0x58 и увеличивается до 0x5F, pop diчтобы завершить первый base-16. Шестнадцатеричная последовательность поддерживается 8 регистрами на размер.


источник
2
В x86 / 64 префиксы инструкций REX расширяют индексы регистров большим количеством битов.
Алексей Фрунзе