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

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

4 ответов


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

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

в наши дни у нас действительно много регистров - они просто не запрограммированы явно. Мы есть "регистрация переименования". Хотя вы получаете доступ только к небольшому набору (8-32 регистрам), они фактически поддерживаются гораздо большим набором (e.g 64-256). Затем процессор отслеживает видимость каждого регистра и выделяет их переименованному набору. Например, вы можете загружать, изменять, а затем хранить в регистре много раз подряд, и каждая из этих операций фактически выполняется независимо в зависимости от пропусков кэша и т. д. В руке:

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". Между тем, мы также снова выполняем загрузку/изменение/store в 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 - это гигантское количество переименованных регистров в эти дни (ballpark 256). Это означало бы иметь 8 бит раз 2 для каждой инструкции, чтобы просто сказать, что такое источник и назначение. Это значительно увеличило бы количество проводов, необходимых для сердечника, и его размер. Таким образом, есть сладкое пятно вокруг 16-32 регистров, на которые большинство дизайнеров согласились, а для неупорядоченных проектов CPU зарегистрируйте переименование-это способ смягчить его.

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

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

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


мы Do есть больше из них

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

но, оказывается, спасибо зарегистрировать переименование, у нас действительно есть много доступных регистров, и нам даже не нужно их сохранять. CPU фактически имеет много наборов регистров, и он автоматически переключается между ними как ваш код exeutes. Он делает это исключительно для того, чтобы получить больше регистров.

пример:

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


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


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