Ответ 1
Есть много причин, по которым у вас не просто огромное количество регистров:
- Они очень связаны с большинством этапов трубопровода. Для начала вам нужно отслеживать их продолжительность жизни и перенаправлять результаты на предыдущие этапы. Сложность становится трудноразрешимой очень быстро, и количество проводов (в буквальном смысле) растет с одинаковой скоростью. Это дорого стоит на площади, что в конечном итоге означает, что это дорого стоит по мощности, цене и производительности после определенного момента.
- Он занимает пространство кодирования команд. 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 в настоящее время занимает огромное количество переименованных регистров (ballpark 256). Это означало бы 8 бит раз 2 для каждой инструкции, чтобы просто сказать, что такое источник и место назначения. Это значительно увеличило бы количество проводов, необходимых по всему ядру, и его размер. Итак, там сладостное пятно вокруг регистров 16-32, которое большинство дизайнеров устроило, и для нестандартных процессоров, переименование регистров - способ смягчить его.
Изменить: важность выполнения внеочередного исполнения и переименование регистра. Когда у вас есть OOO, количество регистров не имеет большого значения, потому что они всего лишь "временные метки" и переименовываются в гораздо более широкий набор виртуальных регистраций. Вы не хотите, чтобы число было слишком маленьким, потому что трудно писать небольшие последовательности кода. Это проблема для x86-32, потому что ограниченные 8 регистров означают, что многие временные пользователи проходят через стек, а ядре требуется дополнительная логика для пересылки операций чтения/записи в память. Если у вас нет OOO, вы обычно говорите о небольшом ядре, и в этом случае большой набор регистров является плохим издержками/производительностью.
Итак, есть естественное сладкое пятно для размера банка регистров, которое максимизируется на уровне около 32 архивированных регистров для большинства классов ЦП. x86-32 имеет 8 регистров, и это определенно слишком мало. ARM отправился с 16 регистрами, и это хороший компромисс. 32 регистров немного больше, чем угодно - вам не нужны последние 10 или около того.
Ничто из этого не касается дополнительных регистров, которые вы получаете для SSE и других векторных сопроцессоров с плавающей запятой. Они имеют смысл в качестве дополнительного набора, потому что они работают независимо от целочисленного ядра и не увеличивают сложность процессора по экспоненте.