Jest wiele powodów, dla których nie masz tylko dużej liczby rejestrów:
- Są silnie powiązane z większością etapów rurociągu. Na początek musisz śledzić ich żywotność i przesuwać wyniki z powrotem do poprzednich etapów. Złożoność staje się trudna do rozwiązania bardzo szybko, a liczba zaangażowanych drutów (dosłownie) rośnie w tym samym tempie. Jest drogie pod względem powierzchni, co ostatecznie oznacza, że po pewnym czasie jest drogie pod względem mocy, ceny i wydajności.
- Zajmuje miejsce na kodowanie instrukcji. 16 rejestrów zajmuje 4 bity dla źródła i celu, a kolejne 4, jeśli masz instrukcje 3-operandowe (np. ARM). To bardzo dużo miejsca na kodowanie zestawu instrukcji, które zajmuje tylko określenie rejestru. To ostatecznie wpływa na dekodowanie, rozmiar kodu i ponownie złożoność.
- Są lepsze sposoby na osiągnięcie tego samego rezultatu ...
Obecnie naprawdę mamy dużo rejestrów - po prostu nie są one jawnie zaprogramowane. Mamy "zmianę nazwy rejestru". Chociaż masz dostęp tylko do małego zestawu (8-32 rejestrów), w rzeczywistości są one obsługiwane przez znacznie większy zestaw (np. 64-256). Następnie CPU śledzi widoczność każdego rejestru i przydziela je do zestawu o zmienionej nazwie. Na przykład, możesz ładować, modyfikować, a następnie przechowywać w rejestrze wiele razy z rzędu i mieć każdą z tych operacji faktycznie wykonywanych niezależnie w zależności od błędów pamięci podręcznej itp. W ARM:
ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]
Rdzenie Cortex A9 zmieniają nazwy rejestrów, więc pierwsze ładowanie do "r0" trafia do wirtualnego rejestru o zmienionej nazwie - nazwijmy go "v0". Ładowanie, zwiększanie i zapisywanie odbywa się na „v0”. W międzyczasie ponownie wykonujemy ładowanie / modyfikowanie / zapisywanie do r0, ale nazwa zostanie zmieniona na „v1”, ponieważ jest to całkowicie niezależna sekwencja wykorzystująca r0. Powiedzmy, że ładowanie ze wskaźnika w "r4" utknęło z powodu braku pamięci podręcznej. W porządku - nie musimy czekać, aż "r0" będzie gotowe. Ponieważ ma zmienioną nazwę, możemy uruchomić następną sekwencję z "v1" (również odwzorowanym na r0) - i być może jest to trafienie w pamięć podręczną i właśnie odnieśliśmy ogromną wygraną w wydajności.
ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]
Myślę, że x86 ma obecnie do gigantycznej liczby zmienionych nazw rejestrów (ballpark 256). Oznaczałoby to posiadanie 8 bitów razy 2 dla każdej instrukcji tylko po to, aby powiedzieć, jakie jest źródło i cel. Zwiększyłoby to znacznie liczbę przewodów potrzebnych w rdzeniu i jego rozmiar. Tak więc istnieje dobre miejsce w okolicach 16-32 rejestrów, na które zdecydowała się większość projektantów, aw przypadku niedziałających projektów procesorów zmiana nazwy rejestrów jest sposobem na złagodzenie tego problemu.
Edycja : znaczenie wykonywania poza kolejnością i zmiany nazwy rejestru w tym. Gdy masz OOO, liczba rejestrów nie ma tak dużego znaczenia, ponieważ są to tylko „tymczasowe znaczniki”, których nazwa zostaje zmieniona na znacznie większy zestaw rejestrów wirtualnych. Nie chcesz, aby liczba była zbyt mała, ponieważ pisanie małych sekwencji kodu jest trudne. Jest to problem dla x86-32, ponieważ ograniczone 8 rejestrów oznacza, że wiele danych tymczasowych przechodzi przez stos, a rdzeń potrzebuje dodatkowej logiki, aby przekazywać odczyty / zapisy do pamięci. Jeśli nie masz OOO, zwykle mówisz o małym rdzeniu, w którym to przypadku duży zestaw rejestrów to słaba korzyść koszt / wydajność.
Jest więc naturalny punkt optymalny dla rozmiaru banku rejestrów, który wynosi maksymalnie około 32 zaprojektowanych rejestrów dla większości klas procesorów. x86-32 ma 8 rejestrów i jest zdecydowanie za mały. ARM poszedł z 16 rejestrami i to dobry kompromis. 32 rejestry to trochę za dużo, jeśli w ogóle - w końcu nie potrzebujesz ostatnich 10 lub więcej.
Nic z tego nie dotyczy dodatkowych rejestrów, które otrzymujesz dla SSE i innych koprocesorów zmiennoprzecinkowych wektorów. Mają sens jako dodatkowy zestaw, ponieważ działają niezależnie od rdzenia typu integer i nie zwiększają wykładniczo złożoności procesora.