Co register
słowo kluczowe robi w języku C? Czytałem, że jest on wykorzystywany do optymalizacji, ale nie jest jasno zdefiniowany w żadnym standardzie. Czy nadal jest to istotne, a jeśli tak, to kiedy go użyjesz?
register
zmiennej.
Co register
słowo kluczowe robi w języku C? Czytałem, że jest on wykorzystywany do optymalizacji, ale nie jest jasno zdefiniowany w żadnym standardzie. Czy nadal jest to istotne, a jeśli tak, to kiedy go użyjesz?
register
zmiennej.
Odpowiedzi:
Jest to wskazówka dla kompilatora, że zmienna będzie intensywnie używana i zaleca się, aby w miarę możliwości przechowywać ją w rejestrze procesora.
Większość współczesnych kompilatorów robi to automatycznie i lepiej je wybiera niż my ludzie.
register
pobieraniu adresu zmiennej; jest to jedyny obowiązkowy efekt register
słowa kluczowego. Nawet to wystarcza, aby poprawić optymalizacje, ponieważ powiedzenie, że zmienną można modyfikować tylko w ramach tej funkcji, staje się banalne.
Dziwi mnie, że nikt nie wspomniał, że nie można wziąć adresu zmiennej rejestru, nawet jeśli kompilator zdecyduje się zachować zmienną w pamięci, a nie w rejestrze.
Więc za pomocą register
nic nie wygrywasz (zresztą kompilator sam decyduje, gdzie umieścić zmienną) i traci &
operatora - nie ma powodu, aby go używać.
register
jest to przydatne, nawet jeśli kompilator nie umieści go w rejestrze.
const
jest również bezużyteczna, ponieważ nic ci nie wygrywa, tracisz jedynie możliwość zmiany zmiennej. register
można użyć, aby upewnić się, że nikt nie przyjmie adresu zmiennej w przyszłości bez zastanowienia. Nigdy nie miałem jednak powodu, aby z tego korzystać register
.
Mówi kompilatorowi, aby spróbował użyć rejestru procesora zamiast pamięci RAM do przechowywania zmiennej. Rejestry znajdują się w procesorze i dostęp do nich jest znacznie szybszy niż w przypadku pamięci RAM. Jest to jednak tylko sugestia dla kompilatora i może nie zostać zrealizowana.
Wiem, że to pytanie dotyczy C, ale to samo pytanie dla C ++ zostało zamknięte jako dokładna kopia tego pytania. Ta odpowiedź może zatem nie dotyczyć C.
Najnowszy projekt standardu C ++ 11, N3485 , mówi o tym w 7.1.1 / 3:
Specyfikator
register
jest wskazówką dla implementacji, że tak zadeklarowana zmienna będzie intensywnie używana. [ uwaga: podpowiedź można zignorować, aw większości implementacji zostanie ona zignorowana, jeśli adres zmiennej zostanie przyjęty. To użycie jest przestarzałe ... - uwaga ]
W C ++ (ale nie w C) standard nie stwierdza, że nie można pobrać adresu deklarowanej zmiennej register
; jednak ponieważ zmienna przechowywana w rejestrze procesora przez cały okres jej istnienia nie ma powiązanej z nią lokalizacji w pamięci, próba przejęcia jego adresu byłaby nieprawidłowa, a kompilator zignoruje register
słowo kluczowe, aby umożliwić przyjęcie adresu.
Nie było to istotne przez co najmniej 15 lat, ponieważ optymalizatorzy podejmują lepsze decyzje w tej sprawie niż możesz. Nawet gdy było to istotne, miało to o wiele większy sens w architekturze procesora z dużą liczbą rejestrów, takich jak SPARC lub M68000, niż w przypadku Intel z niewielką liczbą rejestrów, z których większość jest zarezerwowana przez kompilator na własne potrzeby.
W rzeczywistości rejestr informuje kompilator, że zmienna nie ma aliasu z niczym innym w programie (nawet char).
To może być wykorzystywane przez współczesne kompilatory w różnych sytuacjach i może pomóc kompilatorowi w złożonym kodzie - w prostym kodzie kompilatory mogą to rozwiązać samodzielnie.
W przeciwnym razie nie służy celowi i nie jest wykorzystywany do przydzielania rejestrów. Zazwyczaj nie powoduje to obniżenia wydajności, o ile kompilator jest wystarczająco nowoczesny.
register
zabrania w ogóle przyjmowania adresu, ponieważ w przeciwnym razie przydatne może być poinformowanie kompilatorów o przypadkach, w których kompilator byłby w stanie zastosować optymalizację rejestru pomimo przekazania adresu zmiennej do funkcji zewnętrznej (zmienna musiałaby zostać opróżnione do pamięci dla tego konkretnego wywołania , ale gdy funkcja wróci, kompilator może ponownie potraktować ją jako zmienną, której adresu nigdy nie podano.
bar
jest to register
zmienna, kompilator może w swoim wolnym zastąpić foo(&bar);
z int temp=bar; foo(&temp); bar=temp;
, ale biorąc pod adres bar
byłyby zakazane w większości innych kontekstach nie wydawać się zbyt skomplikowane reguły. Jeśli zmienna mogłaby być przechowywana w rejestrze, podstawienie zmniejszyłoby kod. Jeśli zmienna i tak musiałaby być przechowywana w pamięci RAM, podstawienie zwiększyłoby kod. Pozostawienie pytania, czy zastąpić kompilator, doprowadziłoby do lepszego kodu w obu przypadkach.
register
kwalifikację zmiennych globalnych, niezależnie od tego, czy kompilator zezwala na pobranie adresu, pozwoliłoby na pewne dobre optymalizacje w przypadkach, gdy funkcja wbudowana, która używa zmiennej globalnej, jest wywoływana wielokrotnie w pętli. Nie mogę wymyślić żadnego innego sposobu, aby ta zmienna była przechowywana w rejestrze między iteracjami pętli - prawda?
Czytałem, że jest on wykorzystywany do optymalizacji, ale nie jest jasno zdefiniowany w żadnym standardzie.
W rzeczywistości jest to wyraźnie określone przez standard C. Cytując wersję roboczą N1570 sekcja 6.7.1 akapit 6 (inne wersje mają to samo brzmienie):
Deklaracja identyfikatora obiektu ze specyfikatorem klasy pamięci
register
sugeruje, że dostęp do obiektu powinien być jak najszybszy. Zakres, w jakim takie sugestie są skuteczne, zależy od wdrożenia.
Jednoargumentowy &
operator nie może być stosowany do obiektu zdefiniowanego za pomocą register
i register
nie może być stosowany w deklaracji zewnętrznej.
Istnieje kilka innych (dość niejasnych) reguł, które są specyficzne dla register
obiektów zakwalifikowanych:
register
niezdefiniowanego zachowania. register
, ale nie można zrobić nic użytecznego z takim obiektem (indeksowanie do tablicy wymaga przyjęcia adresu jego początkowego elementu)._Alignas
(nowy w C11) nie może być zastosowany do takiego obiektu.va_start
makra jest register
-kwalifikowana, zachowanie jest niezdefiniowane.Może być kilka innych; pobierz projekt standardu i wyszukaj „zarejestruj się”, jeśli jesteś zainteresowany.
Jak sama nazwa wskazuje, pierwotnym znaczeniem register
było wymaganie, aby obiekt był przechowywany w rejestrze procesora. Jednak dzięki ulepszeniom w optymalizacji kompilatorów stało się to mniej przydatne. Nowoczesne wersje standardu C nie odnoszą się do rejestrów procesora, ponieważ nie muszą już (trzeba) zakładać, że istnieje coś takiego (istnieją architektury, które nie używają rejestrów). Powszechnie wiadomo, że zastosowanie register
do deklaracji obiektowej z większym prawdopodobieństwem pogorszy generowany kod, ponieważ zakłóca to alokację rejestru przez kompilator. Może być jeszcze kilka przypadków, w których jest to przydatne (powiedzmy, jeśli naprawdę wiesz, jak często zmienna będzie dostępna, a twoja wiedza jest lepsza niż to, co może zrozumieć nowoczesny kompilator optymalizujący).
Głównym namacalnym efektem register
jest to, że uniemożliwia jakąkolwiek próbę przejęcia adresu obiektu. Nie jest to szczególnie przydatne jako wskazówka optymalizacyjna, ponieważ można ją zastosować tylko do zmiennych lokalnych, a kompilator optymalizujący może przekonać się, że adres takiego obiektu nie jest brany.
register
niewykwalifikowanego obiektu tablicowego, jeśli tak myślisz.
register
obiekty tablicowe; zobacz zaktualizowany pierwszy punkt w mojej odpowiedzi. Zdefiniowanie takiego obiektu jest legalne, ale nie można z nim nic zrobić. Jeśli dodasz register
do definicji s
w swoim przykładzie , program jest nielegalny (naruszenie ograniczenia) w C. C ++ nie nakłada takich samych ograniczeń register
, więc program będzie poprawny w C ++ (ale używanie register
byłoby bezcelowe).
register
kluczowe mogłoby służyć pożytecznemu celowi, gdyby przyjęcie adresu takiej zmiennej było zgodne z prawem, ale tylko w przypadkach, w których semantyka nie miałaby wpływu na kopiowanie zmiennej do tymczasowej, gdy jej adres jest pobierany, i ponowne ładowanie jej z tymczasowej w następnym punkcie sekwencji. Pozwoliłoby to kompilatorom założyć, że zmienna może być bezpiecznie przechowywana w rejestrze we wszystkich dostępach do wskaźnika, pod warunkiem że zostanie opróżniona w dowolnym miejscu, w którym zostanie pobrany adres.
Opowiadanie!
C, jako język, jest abstrakcją komputera. Umożliwia robienie rzeczy w zakresie tego, co robi komputer, tj. Manipulowanie pamięcią, matematyka, drukowanie itp.
Ale C to tylko abstrakcja. I w końcu wyciąga z ciebie język asemblera. Asembler to język, który czyta procesor, a jeśli go używasz, robisz różne rzeczy pod względem procesora. Co robi procesor? Zasadniczo czyta z pamięci, robi matematykę i zapisuje w pamięci. Procesor nie tylko wykonuje matematykę na liczbach w pamięci. Najpierw musisz przenieść liczbę z pamięci do pamięci wewnątrz procesora o nazwie a rejestrem. Gdy skończysz robić wszystko, co musisz zrobić z tym numerem, możesz przenieść go z powrotem do normalnej pamięci systemowej. Po co w ogóle korzystać z pamięci systemowej? Liczba rejestrów jest ograniczona. W nowoczesnych procesorach dostajesz tylko około sto bajtów, a starsze popularne procesory były jeszcze bardziej fantastycznie ograniczone (6502 miał 3 8-bitowe rejestry do bezpłatnego użytku). Twoja średnia operacja matematyczna wygląda następująco:
load first number from memory
load second number from memory
add the two
store answer into memory
Wiele z nich to ... nie matematyka. Te operacje ładowania i przechowywania mogą zająć nawet połowę czasu przetwarzania. C, będący abstrakcją komputerów, uwolnił programistę od zmartwień związanych z używaniem i żonglowaniem rejestrami, a ponieważ liczba i typ różnią się między komputerami, C nakłada odpowiedzialność za przydzielanie rejestrów wyłącznie na kompilator. Z jednym wyjątkiem.
Kiedy deklarujesz zmienną register
, mówisz kompilatorowi: „Tak, zamierzam, aby ta zmienna była często używana i / lub krótkotrwała. Gdybym był tobą, spróbowałbym zachować ją w rejestrze”. Kiedy standard C mówi, że kompilatory nie muszą właściwie nic robić, to dlatego, że standard C nie wie, dla jakiego komputera kompilujesz, i może być tak jak w 6502 powyżej, gdzie wszystkie 3 rejestry są potrzebne tylko do działania , i nie ma rezerwowego rejestru, aby zachować Twój numer. Jednak gdy mówi, że nie możesz wziąć adresu, to dlatego, że rejestry nie mają adresów. To ręce procesora. Ponieważ kompilator nie musi podawać adresu, a ponieważ nigdy nie może mieć adresu, dla niego dostępnych jest kilka optymalizacji. Może, powiedzmy, zawsze przechowywać numer w rejestrze. Nie robi muszę się martwić, gdzie jest przechowywany w pamięci komputera (poza koniecznością odzyskania go ponownie). Może nawet przebić ją do innej zmiennej, przekazać innemu procesorowi, zmienić lokalizację itp.
tl; dr: Zmienne krótkotrwałe, które wykonują wiele obliczeń matematycznych. Nie deklaruj zbyt wielu naraz.
Masz problem z wyrafinowanym algorytmem kolorowania grafik kompilatora. Służy do przydzielania rejestrów. Cóż, głównie. Działa jako wskazówka dla kompilatora - to prawda. Ale nie ignorowane w całości, ponieważ nie wolno ci przyjmować adresu zmiennej rejestru (pamiętaj, że kompilator, teraz na twoją łaskę, spróbuje działać inaczej). Co w pewien sposób mówi ci, żebyś go nie używał.
Słowo kluczowe zostało użyte dawno, dawno temu. Gdy rejestrów było tak mało, że można je było policzyć za pomocą palca wskazującego.
Ale, jak powiedziałem, przestarzałe nie oznacza, że nie możesz go użyć.
Tylko mała wersja demonstracyjna (bez żadnego rzeczywistego celu) do porównania: podczas usuwania register
słów kluczowych przed każdą zmienną ten fragment kodu zajmuje 3,41 sekundy na moim i7 (GCC), a register
ten sam kod kończy się w 0,7 sekundy.
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
Testowałem słowo kluczowe register pod QNX 6.5.0, używając następującego kodu:
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
Mam następujące wyniki:
-> upłynęło 807679611 cykli
-> Ten system ma 3300830000 cykli na sekundę
-> Cykle w sekundach wynoszą ~ 0,244600
A teraz bez rejestracji int:
int a=0, b = 1, c = 3, i;
Mam:
-> 1421694077 cykli, które upłynęły
-> Ten system ma 3300830000 cykli na sekundę
-> Cykle w sekundach wynoszą ~ 0,430700
Rejestr poinformuje kompilator, że programista uważa, że zmienna ta zostanie zapisana / odczytana w stopniu wystarczającym do uzasadnienia jej przechowywania w jednym z niewielu rejestrów dostępnych do wykorzystania w zmiennej. Odczyt / zapis z rejestrów jest zwykle szybszy i może wymagać mniejszego zestawu kodów operacyjnych.
W dzisiejszych czasach nie jest to zbyt przydatne, ponieważ większość optymalizatorów kompilatorów jest lepsza od ciebie w określaniu, czy rejestr powinien być używany dla tej zmiennej i na jak długo.
W latach siedemdziesiątych, na samym początku języka C, wprowadzono słowo kluczowe register, aby programista mógł udzielać wskazówek kompilatorowi, informując go, że zmienna będzie używana bardzo często i że należy mądrze zachowaj jego wartość w jednym z wewnętrznych rejestrów procesora.
W dzisiejszych czasach optymalizatory są znacznie bardziej wydajne niż programiści w zakresie określania zmiennych, które prawdopodobnie będą przechowywane w rejestrach, a optymalizator nie zawsze bierze pod uwagę wskazówkę programisty.
Tak wiele osób niesłusznie nie zaleca używania słowa kluczowego register.
Zobaczmy dlaczego!
Słowo kluczowe rejestru ma powiązany efekt uboczny: nie można odwoływać się (uzyskać adres) zmiennej typu rejestru.
Ludzie odradzający innym nieużywanie rejestrów błędnie traktują to jako dodatkowy argument.
Jednak sam fakt, że nie można pobrać adresu zmiennej rejestru, pozwala kompilatorowi (i jego optymalizatorowi) wiedzieć, że wartości tej zmiennej nie można modyfikować pośrednio za pomocą wskaźnika.
Gdy w pewnym punkcie strumienia instrukcji zmienna rejestru ma przypisaną wartość w rejestrze procesora, a rejestr nie był używany, ponieważ do uzyskania wartości innej zmiennej kompilator wie, że nie musi ponownie ładować wartość zmiennej w tym rejestrze. Pozwala to uniknąć kosztownego, bezużytecznego dostępu do pamięci.
Wykonaj własne testy, a uzyskasz znaczną poprawę wydajności w najbardziej wewnętrznych pętlach.
Kompilator Visual C ++ firmy Microsoft ignoruje register
słowo kluczowe, gdy włączona jest optymalizacja globalnej alokacji rejestru (flaga kompilatora / Oe).
Zobacz rejestracja słowa kluczowego w MSDN.
Słowo kluczowe register mówi kompilatorowi, aby zapisał określoną zmienną w rejestrach procesora, aby była szybko dostępna. Z punktu widzenia programisty słowo kluczowe register jest używane do zmiennych, które są często używane w programie, dzięki czemu kompilator może przyspieszyć kod. Chociaż zależy od kompilatora, czy zachować zmienną w rejestrach procesora czy w pamięci głównej.
Rejestr wskazuje kompilatorowi na optymalizację tego kodu poprzez zapisanie tej konkretnej zmiennej w rejestrach, a następnie w pamięci. jest to żądanie do kompilatora, kompilator może, ale nie musi, rozważyć to żądanie. Możesz skorzystać z tej funkcji, gdy bardzo często uzyskuje się dostęp do niektórych zmiennych. Na przykład: Pętla.
Jeszcze jedną rzeczą jest to, że jeśli zadeklarujesz zmienną jako rejestr, nie możesz uzyskać jej adresu, ponieważ nie jest ona przechowywana w pamięci. otrzymuje swój przydział w rejestrze procesora.
Dane wyjściowe asm gcc 9.3, bez użycia flag optymalizacji (wszystko w tej odpowiedzi dotyczy standardowej kompilacji bez flag optymalizacji):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
Wymusza ebx
to użycie w obliczeniach, co oznacza, że należy go wepchnąć na stos i przywrócić na końcu funkcji, ponieważ jest on zapisany przez callee. register
produkuje więcej wierszy kodu i 1 zapis pamięci i 1 odczyt pamięci (choć realistycznie można by to zoptymalizować do 0 R / Ws, gdyby obliczenia zostały wykonane esi
, co dzieje się przy użyciu C ++ const register
). Nieużywanie register
powoduje 2 zapisy i 1 odczyt (chociaż przekierowanie z pamięci do załadowania nastąpi podczas odczytu). Wynika to z faktu, że wartość musi być obecna i aktualizowana bezpośrednio na stosie, aby można było odczytać poprawną wartość według adresu (wskaźnika). register
nie ma tego wymogu i nie można go wskazać. const
i register
są w zasadzie przeciwieństwem volatile
i używaniemvolatile
zastąpi optymalizacje const w zakresie plików i bloków oraz register
optymalizacje w zakresie bloków. const register
i register
wygeneruje identyczne wyniki, ponieważ const nic nie robi na C w zakresie bloków, więc register
zastosowanie mają tylko optymalizacje.
Podczas clang register
jest ignorowany, ale const
nadal występują optymalizacje.