В 32-битном мы имели 8 регистров общего назначения. С 64-битной суммой удваивается, но она кажется независимой от самого 64-битного изменения.
Теперь, если регистры настолько быстры (без доступа к памяти), почему их больше нет? Не должны ли сборщики ЦП работать как можно больше регистров в ЦП? Каково логическое ограничение того, почему у нас есть только то количество, которое у нас есть?
Если регистры настолько стремительно быстры, почему бы нам не узнать их больше?
Ответ 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 и других векторных сопроцессоров с плавающей запятой. Они имеют смысл в качестве дополнительного набора, потому что они работают независимо от целочисленного ядра и не увеличивают сложность процессора по экспоненте.
Ответ 2
У нас их больше
Поскольку почти каждая команда должна выбрать 1, 2 или 3 архитектурно видимых регистра, расширение числа из них увеличит размер кода на несколько бит на каждой инструкции и, следовательно, уменьшит плотность кода. Он также увеличивает количество context, которое должно быть сохранено как состояние потока и частично сохранено в функции записи активации. Эти операции происходят часто. Блокировки трубопроводов должны проверять табло для каждого регистра, и это имеет квадратичную сложность времени и пространства. И, возможно, самая большая причина - просто совместимость с уже определенным набором команд.
Но, благодаря переименованию переименования, у нас действительно есть много доступных регистров, и нам даже не нужно их сохранять. На самом деле у CPU есть множество наборов регистров, и он автоматически переключается между ними по мере того, как ваш код работает. Он делает это исключительно, чтобы получить больше регистров.
Пример:
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 никогда не будет использоваться снова после первого хранилища. Это позволяет отложить первую нагрузку (даже если нагрузка на кристалле на кристалле обычно занимает несколько циклов), не требуя задержки второй нагрузки или второго хранилища.
Ответ 3
Они постоянно добавляют регистры, но часто привязаны к специальным инструкциям (например, SIMD, SSE2 и т.д.) или требуют компиляции для конкретной архитектуры процессора, что снижает переносимость. Существующие инструкции часто работают над конкретными регистрами и не могут использовать другие регистры, если они доступны. Набор устаревших инструкций и все.
Ответ 4
Чтобы добавить немного интересной информации, вы заметите, что наличие 8 регистров одинакового размера позволяет кодам операций поддерживать согласованность с шестнадцатеричной нотацией. Например, инструкция push ax
- это код операции 0x50 на x86 и до 0x57 для последнего регистра di. Затем команда pop ax
начинается с 0x58 и доходит до 0x5F pop di
для завершения первой базы-16. Шестнадцатеричная консистенция поддерживается с 8 регистрами на размер.