레지스터가 엄청나게 빠르다면 왜 더 많이 가지지 않습니까?


88

32 비트에서는 8 개의 "범용"레지스터가있었습니다. 64 비트를 사용하면 양이 두 배가되지만 64 비트 변경 자체와는 독립적 인 것 같습니다.
이제 레지스터가 너무 빠르면 (메모리 액세스 없음) 왜 자연스럽게 더 많은 레지스터가 없을까요? CPU 빌더는 CPU에서 가능한 한 많은 레지스터를 작동해야하지 않습니까? 우리가 가진 양만 가지고있는 이유에 대한 논리적 제한은 무엇입니까?


CPU와 GPU는 주로 캐시와 대규모 멀티 스레딩을 통해 대기 시간을 숨 깁니다. 따라서 CPU에는 레지스터가 거의 없거나 필요하지만 GPU에는 수만 개의 레지스터가 있습니다. 이러한 모든 장단점과 요인을 논의하는 GPU 레지스터 파일에 대한 설문 조사 문서를 참조하십시오 .
user984260

답변:


119

레지스터 수가 많지 않은 데는 여러 가지 이유가 있습니다.

  • 대부분의 파이프 라인 단계와 밀접하게 연결되어 있습니다. 우선, 수명을 추적하고 결과를 이전 단계로 되돌려 야합니다. 복잡성은 매우 빠르게 처리하기 어려워지고 관련된 와이어의 수 (문자 그대로)도 같은 속도로 증가합니다. 지역적으로 비싸기 때문에 궁극적으로 특정 시점 이후에는 전력, 가격 및 성능에 비쌉니다.
  • 명령어 인코딩 공간을 차지합니다. 16 개의 레지스터는 소스 및 대상에 대해 4 비트를 사용하고 3 연산 명령어 (예 : ARM)가있는 경우 다른 4 비트를 사용합니다. 그것은 단지 레지스터를 지정하기 위해 차지하는 엄청난 양의 명령어 세트 인코딩 공간입니다. 이는 결국 디코딩, 코드 크기 및 복잡성에 영향을 미칩니다.
  • 같은 결과를 얻는 더 좋은 방법이 있습니다 ...

요즘 우리는 정말 많은 레지스터를 가지고 있습니다-그것들은 단지 명시 적으로 프로그래밍 된 것이 아닙니다. "등록 이름 변경"이 있습니다. 작은 세트 (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의 복잡성을 기하 급수적으로 증가시키지 않기 때문에 추가 세트로 합리적입니다.


12
탁월한 답변-또 다른 이유를 믹스에 넣고 싶습니다. 레지스터가 많을수록 컨텍스트 전환시 스택에 던지거나 끌어내는 데 더 많은 시간이 걸립니다. 확실히 주요 문제는 아니지만 고려 사항입니다.
Will A

7
@WillA 좋은 지적입니다. 그러나 레지스터가 많은 아키텍처에는이 비용을 줄일 수있는 방법이 있습니다. ABI는 일반적으로 대부분의 레지스터에 대한 호출 수신자 저장 기능을 가지므로 코어 세트 만 저장하면됩니다. 컨텍스트 전환은 일반적으로 추가 저장 / 복원 비용이 다른 모든 레드 테이프에 비해 비용이 많이 들지 않을 정도로 비용이 많이 듭니다. SPARC는 실제로 레지스터 뱅크를 메모리 영역의 "윈도우"로 만들어서이 문제를 해결합니다. 그래서 이것은 다소 확장됩니다.
John Ripley

4
내가 확실히 예상하지 못했던 철저한 대답에 의해 내 마음이 날아 갔다고 생각하십시오. 또한 왜 그렇게 많은 명명 된 레지스터가 필요하지 않은지에 대한 설명에 감사드립니다. 매우 흥미 롭습니다! 나는 "내부"에서 일어나는 일에 전적으로 관심이 있기 때문에 당신의 대답을 읽는 것을 정말 즐겼습니다. :) 답을 받기 전에 조금 더 기다릴게요. 왜냐하면 당신은 알지 못하기 때문입니다.하지만 제 +1은 확실합니다.
Xeo

1
레지스터 저장에 대한 책임이 어디에 있든 상관없이 소요되는 시간은 관리 오버 헤드입니다. 좋습니다. 따라서 컨텍스트 전환이 가장 자주 발생하는 경우는 아니지만 인터럽트는 발생합니다. 수동으로 코딩 된 루틴은 레지스터를 절약 할 수 있지만 드라이버가 C로 작성된 경우 인터럽트 선언 함수가 모든 단일 레지스터를 저장하고 isr을 호출 한 다음 저장된 모든 레지스터를 복원 할 가능성이 있습니다. IA-32는 RISC 아키텍처의 32 개 이상의 reg에 비해 15-20 개의 reg로 인터럽트 이점이있었습니다.
Olof Forshell

1
훌륭한 대답이지만 "이름이 변경된"레지스터와 "실제"주소 지정이 가능한 레지스터를 직접 비교하는 데 동의하지 않습니다. x86-32에서는 256 개의 내부 레지스터가 있어도 단일 실행 지점에서 레지스터에 저장된 임시 값을 8 개 이상 사용할 수 없습니다. 기본적으로 레지스터 이름 변경은 OOE의 흥미로운 부산물 일뿐입니다.
noop

12

우리는 수행 더 그들의 유무를

거의 모든 명령어가 구조적으로 볼 수있는 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의 값이 첫 번째 저장 후 다시 사용되지 않는다는 것을 알 수 있습니다. 이를 통해 두 번째로드 또는 두 번째 저장소의 지연없이 첫 번째로드가 지연 될 수 있습니다 (온칩 캐시 적중에도 일반적으로 몇주기가 소요됨).


2

항상 레지스터를 추가하지만 특수 목적 명령어 (예 : SIMD, SSE2 등)에 묶여 있거나 특정 CPU 아키텍처로 컴파일해야하므로 이식성이 떨어집니다. 기존 명령어는 종종 특정 레지스터에서 작동하며 사용 가능한 경우 다른 레지스터를 활용할 수 없습니다. 레거시 명령어 세트 및 모두.


1

여기에 약간의 흥미로운 정보를 추가하려면 8 개의 동일한 크기의 레지스터를 사용하면 opcode가 16 진수 표기법과 일관성을 유지할 수 있음을 알 수 있습니다. 예를 들어 명령어 push ax는 x86에서 opcode 0x50이고 마지막 레지스터 di에 대해 0x57까지 올라갑니다. 그런 다음 명령 pop ax은 0x58에서 시작하여 0x5F까지 올라가 pop di첫 번째 16 진법을 완성합니다. 16 진수 일관성은 크기 당 8 개의 레지스터로 유지됩니다.


2
x86 / 64에서 REX 명령어 접두사는 레지스터 인덱스를 더 많은 비트로 확장합니다.
Alexey Frunze 2012 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.