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