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

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

Ответы [ 4 ]

114 голосов
/ 21 мая 2011

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

  • Они тесно связаны с большинством этапов конвейера. Для начала вам нужно отследить их время жизни и переслать результаты обратно на предыдущие этапы. Сложность становится неразрешимой очень быстро, и количество проводов (в буквальном смысле) растет с той же скоростью. Это дорого по площади, что в конечном итоге означает, что дорого по мощности, цене и производительности после определенного момента.
  • Занимает пространство кодировки команд. 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 регистров, к которым привыкло большинство дизайнеров, а для нестандартных конструкций ЦП переименование регистров является способом смягчить его.

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

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

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

10 голосов
/ 23 мая 2011

Мы Делаем Имеем их больше

Поскольку почти каждая инструкция должна выбирать 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 никогда не используется снова после первого сохранения.Это позволяет отложить первую загрузку (даже попадание в кэш на кристалле обычно занимает несколько циклов), не требуя задержки второй загрузки или второго хранилища.

2 голосов
/ 21 мая 2011

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

1 голос
/ 05 марта 2012

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

...