32 비트에서는 8 개의 "범용"레지스터가있었습니다. 64 비트를 사용하면 양이 두 배가되지만 64 비트 변경 자체와는 독립적 인 것 같습니다.
이제 레지스터가 너무 빠르면 (메모리 액세스 없음) 왜 자연스럽게 더 많은 레지스터가 없을까요? CPU 빌더는 CPU에서 가능한 한 많은 레지스터를 작동해야하지 않습니까? 우리가 가진 양만 가지고있는 이유에 대한 논리적 제한은 무엇입니까?
32 비트에서는 8 개의 "범용"레지스터가있었습니다. 64 비트를 사용하면 양이 두 배가되지만 64 비트 변경 자체와는 독립적 인 것 같습니다.
이제 레지스터가 너무 빠르면 (메모리 액세스 없음) 왜 자연스럽게 더 많은 레지스터가 없을까요? CPU 빌더는 CPU에서 가능한 한 많은 레지스터를 작동해야하지 않습니까? 우리가 가진 양만 가지고있는 이유에 대한 논리적 제한은 무엇입니까?
답변:
레지스터 수가 많지 않은 데는 여러 가지 이유가 있습니다.
요즘 우리는 정말 많은 레지스터를 가지고 있습니다-그것들은 단지 명시 적으로 프로그래밍 된 것이 아닙니다. "등록 이름 변경"이 있습니다. 작은 세트 (8-32 레지스터)에만 액세스하지만 실제로는 훨씬 더 큰 세트 (예 : 64-256)에 의해 지원됩니다. 그런 다음 CPU는 각 레지스터의 가시성을 추적하고 이름이 변경된 세트에 할당합니다. 예를 들어, 레지스터에 여러 번로드, 수정 및 저장할 수 있으며 캐시 미스 등에 따라 이러한 각 작업을 실제로 독립적으로 수행 할 수 있습니다. 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에 대한로드 / 수정 / 저장도 다시 수행하지만, r0을 사용하는 완전히 독립적 인 시퀀스이기 때문에 "v1"로 이름이 변경됩니다. 캐시 미스로 인해 "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 비트 x 2를 갖는 것을 의미합니다. 코어 전체에 필요한 와이어 수와 크기가 크게 늘어납니다. 따라서 대부분의 디자이너가 결정한 16-32 개의 레지스터에 대한 스위트 스팟이 있으며, 비 순차적 인 CPU 디자인의 경우 레지스터 이름 변경이이를 완화하는 방법입니다.
편집 : 순서가 틀린 실행의 중요성과 이에 대한 이름 변경 등록. OOO가 있으면 레지스터의 수는 그다지 중요하지 않습니다. 왜냐하면 그들은 단지 "임시 태그"이고 훨씬 더 큰 가상 레지스터 세트로 이름이 변경되기 때문입니다. 작은 코드 시퀀스를 작성하기가 어려워 지므로 숫자가 너무 작 으면 안됩니다. 이것은 x86-32의 문제입니다. 왜냐하면 제한된 8 개의 레지스터는 많은 임시가 스택을 통과한다는 것을 의미하고 코어는 읽기 / 쓰기를 메모리로 전달하기위한 추가 로직이 필요하기 때문입니다. OOO가 없으면 일반적으로 작은 코어에 대해 이야기합니다.이 경우 큰 레지스터 세트는 낮은 비용 / 성능 이점입니다.
따라서 대부분의 CPU 클래스에 대해 약 32 개의 아키텍처 레지스터로 최대가되는 레지스터 뱅크 크기에 대한 자연스러운 스윗 스팟이 있습니다. x86-32에는 8 개의 레지스터가 있으며 확실히 너무 작습니다. ARM은 16 개의 레지스터를 사용했으며 이는 좋은 타협입니다. 32 개의 레지스터는 약간 너무 많습니다. 마지막 10 개 정도는 필요하지 않습니다.
이 중 어느 것도 SSE 및 기타 벡터 부동 소수점 보조 프로세서에 대해 얻는 추가 레지스터를 건드리지 않습니다. 그것들은 정수 코어와 독립적으로 실행되고 CPU의 복잡성을 기하 급수적으로 증가시키지 않기 때문에 추가 세트로 합리적입니다.
거의 모든 명령어가 구조적으로 볼 수있는 1, 2 또는 3 개의 레지스터를 선택해야하기 때문에 그 수를 늘리면 각 명령어에서 코드 크기가 몇 비트 씩 증가하여 코드 밀도가 감소합니다. 또한 스레드 상태로 저장해야하고 함수의 활성화 레코드 에 부분적으로 저장해야하는 컨텍스트 의 양도 증가합니다 . 이러한 작업은 자주 발생합니다. 파이프 라인 인터록은 모든 레지스터에 대해 점수 판을 확인해야하며 이는 2 차 시간 및 공간 복잡성이 있습니다. 그리고 아마도 가장 큰 이유는 이미 정의 된 명령어 세트와의 호환성 때문일 것입니다.
그러나하기 위해 감사를 밝혀 이름을 변경 등록 , 우리가 정말 가능한 레지스터를 많이해야합니까, 우리는 심지어 저장할 필요가 없습니다. CPU에는 실제로 많은 레지스터 세트가 있으며 코드가 실행될 때 자동으로 이들 사이를 전환합니다. 순전히 더 많은 레지스터를 얻기 위해이 작업을 수행합니다.
예:
load r1, a # x = a
store r1, x
load r1, b # y = b
store r1, y
r0-r7 만있는 아키텍처에서 다음 코드는 CPU에 의해 다음과 같이 자동으로 다시 작성 될 수 있습니다.
load r1, a
store r1, x
load r10, b
store r10, y
이 경우 r10은 r1을 일시적으로 대체하는 숨겨진 레지스터입니다. CPU는 r1의 값이 첫 번째 저장 후 다시 사용되지 않는다는 것을 알 수 있습니다. 이를 통해 두 번째로드 또는 두 번째 저장소의 지연없이 첫 번째로드가 지연 될 수 있습니다 (온칩 캐시 적중에도 일반적으로 몇주기가 소요됨).
여기에 약간의 흥미로운 정보를 추가하려면 8 개의 동일한 크기의 레지스터를 사용하면 opcode가 16 진수 표기법과 일관성을 유지할 수 있음을 알 수 있습니다. 예를 들어 명령어 push ax
는 x86에서 opcode 0x50이고 마지막 레지스터 di에 대해 0x57까지 올라갑니다. 그런 다음 명령 pop ax
은 0x58에서 시작하여 0x5F까지 올라가 pop di
첫 번째 16 진법을 완성합니다. 16 진수 일관성은 크기 당 8 개의 레지스터로 유지됩니다.