Was macht das register
Schlüsselwort in C-Sprache? Ich habe gelesen, dass es zur Optimierung verwendet wird, aber in keinem Standard klar definiert ist. Ist es noch relevant und wenn ja, wann würden Sie es verwenden?
register
Variablen zu erhalten.
Was macht das register
Schlüsselwort in C-Sprache? Ich habe gelesen, dass es zur Optimierung verwendet wird, aber in keinem Standard klar definiert ist. Ist es noch relevant und wenn ja, wann würden Sie es verwenden?
register
Variablen zu erhalten.
Antworten:
Dies ist ein Hinweis für den Compiler, dass die Variable häufig verwendet wird und dass Sie empfehlen, sie nach Möglichkeit in einem Prozessorregister zu speichern.
Die meisten modernen Compiler tun dies automatisch und können sie besser auswählen als wir Menschen.
register
Variablen verwendet wird. Dies ist der einzige obligatorische Effekt des register
Schlüsselworts. Auch dies reicht aus, um Optimierungen zu verbessern, da es trivial wird zu sagen, dass die Variable nur innerhalb dieser Funktion geändert werden kann.
Ich bin überrascht, dass niemand erwähnt hat, dass Sie keine Adresse der Registervariablen verwenden können, selbst wenn der Compiler beschließt, die Variable im Speicher und nicht im Register zu belassen.
Wenn register
Sie also verwenden , gewinnen Sie nichts (der Compiler entscheidet ohnehin selbst, wo die Variable abgelegt werden soll) und verlieren den &
Operator - kein Grund, sie zu verwenden.
register
Dies ist auch dann nützlich, wenn der Compiler es nicht in ein Register einfügt.
const
ist auch nutzlos, da es Ihnen nichts bringt, Sie verlieren nur die Fähigkeit, eine Variable zu ändern. register
könnte verwendet werden, um sicherzustellen, dass in Zukunft niemand mehr die Adresse einer Variablen nimmt, ohne darüber nachzudenken. Ich hatte jedoch nie Grund, es zu benutzen register
.
Es weist den Compiler an, zu versuchen, ein CPU-Register anstelle von RAM zum Speichern der Variablen zu verwenden. Register befinden sich in der CPU und sind viel schneller zugänglich als RAM. Dies ist jedoch nur ein Vorschlag an den Compiler, der möglicherweise nicht umgesetzt wird.
Ich weiß, dass es sich bei dieser Frage um C handelt, aber dieselbe Frage für C ++ wurde als genaues Duplikat dieser Frage geschlossen. Diese Antwort gilt daher möglicherweise nicht für C.
Der neueste Entwurf des C ++ 11-Standards, N3485 , besagt dies in 7.1.1 / 3:
Ein
register
Bezeichner ist ein Hinweis auf die Implementierung, dass die so deklarierte Variable häufig verwendet wird. [ Hinweis: Der Hinweis kann ignoriert werden und wird in den meisten Implementierungen ignoriert, wenn die Adresse der Variablen verwendet wird. Diese Verwendung ist veraltet ... —end note ]
In C ++ (aber nicht in C) gibt der Standard nicht an, dass Sie die Adresse einer deklarierten Variablen nicht übernehmen können register
. Da jedoch einer Variablen, die während ihrer gesamten Lebensdauer in einem CPU-Register gespeichert ist, kein Speicherort zugeordnet ist, ist der Versuch, ihre Adresse zu übernehmen, ungültig, und der Compiler ignoriert das register
Schlüsselwort, um die Adresse zu übernehmen.
Es ist seit mindestens 15 Jahren nicht mehr relevant, da Optimierer diesbezüglich bessere Entscheidungen treffen als Sie. Selbst wenn es relevant war, machte es auf einer CPU-Architektur mit vielen Registern wie SPARC oder M68000 viel mehr Sinn als auf Intel mit seinem Mangel an Registern, von denen die meisten vom Compiler für seine eigenen Zwecke reserviert werden.
Tatsächlich teilt register dem Compiler mit, dass die Variable keinen Alias mit irgendetwas anderem im Programm hat (nicht einmal mit Zeichen).
Dies kann von modernen Compilern in einer Vielzahl von Situationen ausgenutzt werden und dem Compiler bei komplexem Code erheblich helfen - in einfachem Code können die Compiler dies selbst herausfinden.
Andernfalls hat es keinen Zweck und wird nicht für die Registerzuordnung verwendet. Normalerweise führt dies nicht zu Leistungseinbußen bei der Angabe, solange Ihr Compiler modern genug ist.
register
es überhaupt nicht möglich ist, die Adresse zu übernehmen, da es sonst nützlich sein könnte, Compiler über Fälle zu informieren, in denen ein Compiler Registeroptimierungen anwenden kann, obwohl die Adresse einer Variablen an eine externe Funktion übergeben wird (die Variable müsste) für diesen bestimmten Aufruf in den Speicher geleert werden , aber sobald die Funktion zurückkehrt, kann der Compiler sie wieder als Variable behandeln, deren Adresse nie übernommen wurde.
bar
eine ist register
variabel, ein Compiler an seiner Freizeit kann ersetzen foo(&bar);
mit int temp=bar; foo(&temp); bar=temp;
, aber unter der Adresse bar
würde in den meisten anderen Kontexten verboten würde nicht wie eine allzu komplizierte Regel zu sein scheint. Wenn die Variable andernfalls in einem Register gespeichert werden könnte, würde die Ersetzung den Code verkleinern. Wenn die Variable ohnehin im RAM bleiben müsste, würde die Ersetzung den Code vergrößern. Die Frage, ob die Ersetzung dem Compiler überlassen werden soll, würde in beiden Fällen zu besserem Code führen.
register
Qualifizierung für globale Variablen, unabhängig davon, ob der Compiler die Verwendung der Adresse zulässt oder nicht, würde einige nette Optimierungen ermöglichen, wenn eine Inline-Funktion, die eine globale Variable verwendet, wiederholt in einer Schleife aufgerufen wird. Ich kann mir keine andere Möglichkeit vorstellen, diese Variable zwischen Schleifeniterationen in einem Register zu halten - können Sie?
Ich habe gelesen, dass es zur Optimierung verwendet wird, aber in keinem Standard klar definiert ist.
Tatsächlich ist es durch den C-Standard klar definiert. Zitieren des N1570-Entwurfs, Abschnitt 6.7.1, Absatz 6 (andere Versionen haben den gleichen Wortlaut):
Eine Deklaration eines Bezeichners für ein Objekt mit einem Speicherklassenspezifizierer legt
register
nahe, dass der Zugriff auf das Objekt so schnell wie möglich erfolgt. Inwieweit solche Vorschläge wirksam sind, ist umsetzungsdefiniert.
Der unäre &
Operator darf nicht auf ein mit definiertes Objekt angewendet register
und register
nicht in einer externen Deklaration verwendet werden.
Es gibt einige andere (ziemlich undurchsichtige) Regeln, die für register
qualifizierte Objekte spezifisch sind:
register
hat ein undefiniertes Verhalten. register
, aber Sie können mit einem solchen Objekt nichts Nützliches tun (für die Indizierung in ein Array muss die Adresse seines ursprünglichen Elements verwendet werden)._Alignas
Bezeichner (neu in C11) kann möglicherweise nicht auf ein solches Objekt angewendet werden.va_start
Makro register
übergebene Parametername -qualifiziert ist, ist das Verhalten undefiniert.Es kann einige andere geben; Laden Sie einen Entwurf des Standards herunter und suchen Sie bei Interesse nach "Registrieren".
Wie der Name schon sagt, bestand die ursprüngliche Bedeutung von darin register
, dass ein Objekt in einem CPU-Register gespeichert werden muss. Mit Verbesserungen bei der Optimierung von Compilern ist dies jedoch weniger nützlich geworden. Moderne Versionen des C-Standards beziehen sich nicht auf CPU-Register, da sie nicht mehr davon ausgehen (müssen), dass es so etwas gibt (es gibt Architekturen, die keine Register verwenden). Es ist allgemein bekannt, dass das Anwenden register
auf eine Objektdeklaration den generierten Code mit größerer Wahrscheinlichkeit verschlechtert , da dies die Registerzuordnung des Compilers beeinträchtigt. Es kann immer noch einige Fälle geben, in denen dies nützlich ist (z. B. wenn Sie wirklich wissen, wie oft auf eine Variable zugegriffen wird und Ihr Wissen besser ist als das, was ein moderner Optimierungscompiler herausfinden kann).
Der wichtigste greifbare Effekt register
besteht darin, dass verhindert wird, dass versucht wird, die Adresse eines Objekts zu übernehmen. Dies ist als Optimierungshinweis nicht besonders nützlich, da es nur auf lokale Variablen angewendet werden kann und ein Optimierungscompiler selbst feststellen kann, dass die Adresse eines solchen Objekts nicht verwendet wird.
register
qualifiziertes Array-Objekt, wenn Sie das denken.
register
Array-Objekten geirrt . Siehe den aktualisierten ersten Punkt in meiner Antwort. Es ist legal, ein solches Objekt zu definieren, aber Sie können nichts damit anfangen. Wenn Sie register
die Definition von s
in Ihrem Beispiel ergänzen , ist das Programm in C illegal (eine Einschränkungsverletzung). C ++ unterliegt nicht denselben Einschränkungen register
, sodass das Programm in C ++ gültig wäre (die Verwendung register
wäre jedoch sinnlos).
register
Schlüsselwort könnte einen nützlichen Zweck erfüllen, wenn es legal wäre, die Adresse einer solchen Variablen zu übernehmen, jedoch nur in Fällen, in denen die Semantik nicht beeinflusst würde, wenn die Variable bei der Übernahme in eine temporäre Adresse kopiert und aus der temporären Adresse neu geladen wird am nächsten Sequenzpunkt. Dies würde es Compilern ermöglichen anzunehmen, dass die Variable über alle Zeigerzugriffe hinweg sicher in einem Register gespeichert werden kann, vorausgesetzt, sie wird an einer beliebigen Stelle gelöscht, an der ihre Adresse verwendet wird.
Märchenstunde!
C als Sprache ist eine Abstraktion eines Computers. Sie können damit Dinge tun, die ein Computer tut, dh den Speicher manipulieren, rechnen, Dinge drucken usw.
Aber C ist nur eine Abstraktion. Und letztendlich extrahiert es aus Ihnen die Assemblersprache. Assembly ist die Sprache, die eine CPU liest, und wenn Sie sie verwenden, tun Sie Dinge in Bezug auf die CPU. Was macht eine CPU? Grundsätzlich liest es aus dem Speicher, rechnet und schreibt in den Speicher. Die CPU berechnet nicht nur Zahlen im Speicher. Zuerst müssen Sie eine Zahl aus dem Speicher in den Speicher innerhalb der CPU verschieben, die als Register bezeichnet wird. Sobald Sie mit dieser Nummer fertig sind, können Sie sie wieder in den normalen Systemspeicher verschieben. Warum überhaupt Systemspeicher verwenden? Die Anzahl der Register ist begrenzt. In modernen Prozessoren erhalten Sie nur etwa hundert Bytes, und ältere, beliebte Prozessoren waren noch fantastischer eingeschränkt (der 6502 verfügte über 3 8-Bit-Register für Ihre kostenlose Verwendung). Ihre durchschnittliche mathematische Operation sieht also so aus:
load first number from memory
load second number from memory
add the two
store answer into memory
Vieles davon ist ... nicht Mathe. Diese Lade- und Speichervorgänge können bis zur Hälfte Ihrer Verarbeitungszeit in Anspruch nehmen. C, eine Abstraktion von Computern, befreite den Programmierer von der Sorge, Register zu verwenden und zu jonglieren, und da Anzahl und Typ zwischen Computern variieren, überträgt C die Verantwortung für die Registerzuweisung ausschließlich auf den Compiler. Mit einer Ausnahme.
Wenn Sie eine Variable deklarieren register
Sie sagen dem Compiler: "Yo, ich beabsichtige, dass diese Variable häufig verwendet wird und / oder nur von kurzer Dauer ist. Wenn ich Sie wäre, würde ich versuchen, sie in einem Register zu führen." Wenn der C-Standard besagt, dass Compiler eigentlich nichts tun müssen, liegt das daran, dass der C-Standard nicht weiß, für welchen Computer Sie kompilieren, und es könnte wie beim 6502 oben sein, bei dem alle drei Register nur für den Betrieb benötigt werden und es gibt kein Ersatzregister, um Ihre Nummer zu behalten. Wenn jedoch angegeben wird, dass Sie die Adresse nicht übernehmen können, liegt dies daran, dass Register keine Adressen haben. Sie sind die Hände des Prozessors. Da der Compiler Ihnen keine Adresse geben muss und überhaupt keine Adresse haben kann, stehen dem Compiler jetzt mehrere Optimierungen offen. Es könnte zum Beispiel die Nummer immer in einem Register behalten. Tut es nicht' Sie müssen sich keine Gedanken darüber machen, wo es im Computerspeicher gespeichert ist (außer dass Sie es wieder zurückerhalten müssen). Es könnte es sogar in eine andere Variable stempeln, es einem anderen Prozessor geben, ihm einen sich ändernden Ort geben usw.
tl; dr: Kurzlebige Variablen, die viel rechnen. Deklarieren Sie nicht zu viele auf einmal.
Sie spielen mit dem ausgeklügelten Graph-Coloring-Algorithmus des Compilers. Dies wird für die Registerzuordnung verwendet. Meistens. Es dient als Hinweis für den Compiler - das stimmt. Aber nicht in seiner Gesamtheit ignoriert, da Sie nicht die Adresse einer Registervariablen annehmen dürfen (denken Sie daran, dass der Compiler, der jetzt Ihrer Gnade ausgeliefert ist, versuchen wird, anders zu handeln). Was Ihnen in gewisser Weise sagt, dass Sie es nicht verwenden sollen.
Das Schlüsselwort wurde lange, lange zurück verwendet. Wenn es nur so wenige Register gab, die sie alle mit Ihrem Zeigefinger zählen konnten.
Aber wie gesagt, veraltet heißt nicht, dass Sie es nicht verwenden können.
Nur eine kleine Demo (ohne realen Zweck) zum Vergleich: Wenn Sie die register
Schlüsselwörter vor jeder Variablen entfernen , dauert dieser Code auf meinem i7 (GCC) 3,41 Sekunden, wobei register
derselbe Code in 0,7 Sekunden abgeschlossen ist.
#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;
}
Ich habe das Schlüsselwort register unter QNX 6.5.0 mit dem folgenden Code getestet:
#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;
}
Ich habe folgende Ergebnisse erhalten:
-> 807679611 Zyklen verstrichen
-> Dieses System hat 3300830000 Zyklen pro Sekunde
-> Die Zyklen in Sekunden betragen ~ 0,244600
Und jetzt ohne Register int:
int a=0, b = 1, c = 3, i;
Ich habe:
-> 1421694077 verstrichene Zyklen
-> Dieses System hat 3300830000 Zyklen pro Sekunde
-> Die Zyklen in Sekunden betragen ~ 0,430700
Register würde den Compiler benachrichtigen, dass der Codierer glaubte, diese Variable würde genug geschrieben / gelesen, um ihre Speicherung in einem der wenigen Register zu rechtfertigen, die für die Verwendung von Variablen verfügbar sind. Das Lesen / Schreiben aus Registern ist normalerweise schneller und erfordert möglicherweise einen kleineren Op-Code-Satz.
Heutzutage ist dies nicht sehr nützlich, da die Optimierer der meisten Compiler besser als Sie bestimmen können, ob und wie lange ein Register für diese Variable verwendet werden soll.
In den siebziger Jahren, ganz am Anfang der C-Sprache, wurde das Schlüsselwort register eingeführt, damit der Programmierer dem Compiler Hinweise geben kann, dass die Variable sehr häufig verwendet wird und dies sinnvoll sein sollte Behalten Sie den Wert in einem der internen Register des Prozessors.
Heutzutage sind Optimierer viel effizienter als Programmierer, um Variablen zu bestimmen, die mit größerer Wahrscheinlichkeit in Registern gespeichert werden, und der Optimierer berücksichtigt nicht immer den Hinweis des Programmierers.
So viele Leute empfehlen fälschlicherweise, das Schlüsselwort register nicht zu verwenden.
Mal sehen warum!
Das Schlüsselwort register hat einen damit verbundenen Nebeneffekt: Sie können nicht auf eine Registertypvariable verweisen (deren Adresse abrufen).
Menschen, die anderen raten, keine Register zu verwenden, nehmen dies fälschlicherweise als zusätzliches Argument.
Die einfache Tatsache, dass Sie die Adresse einer Registervariablen nicht übernehmen können, ermöglicht es dem Compiler (und seinem Optimierer) jedoch zu wissen, dass der Wert dieser Variablen nicht indirekt über einen Zeiger geändert werden kann.
Wenn an einem bestimmten Punkt des Befehlsstroms einer Registervariable der Wert im Register eines Prozessors zugewiesen wird und das Register nicht verwendet wurde, um den Wert einer anderen Variablen abzurufen, weiß der Compiler, dass er nicht erneut geladen werden muss der Wert der Variablen in diesem Register. Dies ermöglicht es, teuren nutzlosen Speicherzugriff zu vermeiden.
Wenn Sie Ihre eigenen Tests durchführen, erhalten Sie signifikante Leistungsverbesserungen in Ihren innersten Schleifen.
Der Visual C ++ - Compiler von Microsoft ignoriert das register
Schlüsselwort, wenn die globale Optimierung der Registerzuordnung (das Compiler-Flag / Oe) aktiviert ist.
Siehe Register Keyword auf MSDN.
Das Schlüsselwort Register weist den Compiler an, die bestimmte Variable in CPU-Registern zu speichern, damit schnell darauf zugegriffen werden kann. Aus Sicht eines Programmierers wird das Schlüsselwort register für die Variablen verwendet, die in einem Programm häufig verwendet werden, damit der Compiler den Code beschleunigen kann. Obwohl es vom Compiler abhängt, ob die Variable in CPU-Registern oder im Hauptspeicher gespeichert werden soll.
Register gibt dem Compiler an, diesen Code zu optimieren, indem diese bestimmte Variable in Registern und dann im Speicher gespeichert wird. Es handelt sich um eine Anforderung an den Compiler. Der Compiler kann diese Anforderung berücksichtigen oder nicht. Sie können diese Funktion verwenden, wenn auf einige Ihrer Variablen sehr häufig zugegriffen wird. Zum Beispiel: Eine Schleife.
Eine weitere Sache ist, dass wenn Sie eine Variable als Register deklarieren, Sie ihre Adresse nicht erhalten können, da sie nicht im Speicher gespeichert ist. es erhält seine Zuordnung im CPU-Register.
gcc 9.3 asm Ausgabe ohne Verwendung von Optimierungsflags (alles in dieser Antwort bezieht sich auf die Standardkompilierung ohne Optimierungsflags):
#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
Dies erzwingt ebx
die Verwendung für die Berechnung, was bedeutet, dass es auf den Stapel verschoben und am Ende der Funktion wiederhergestellt werden muss, da es als Angerufene gespeichert wird. register
erzeugt mehr Codezeilen und 1 Speicherschreib- und 1 Speicherlesevorgang (obwohl dies realistisch gesehen auf 0 R / Ws optimiert werden könnte, wenn die Berechnung in durchgeführt worden wäre esi
, was mit C ++ geschieht const register
). Wenn Sie nicht verwenden, werden register
2 Schreibvorgänge und 1 Lesevorgang ausgeführt (obwohl beim Lesen eine Weiterleitung zum Laden zum Laden erfolgt). Dies liegt daran, dass der Wert direkt auf dem Stapel vorhanden sein und aktualisiert werden muss, damit der richtige Wert anhand der Adresse (Zeiger) gelesen werden kann. register
hat diese Anforderung nicht und kann nicht darauf hingewiesen werden. const
und register
sind im Grunde das Gegenteil von volatile
und verwendenvolatile
überschreibt die const-Optimierungen im Datei- und Blockbereich und die register
Optimierungen im Blockbereich. const register
und register
erzeugt identische Ausgaben, da const im Blockbereich nichts für C tut, sodass nur die register
Optimierungen gelten.
Beim Klirren register
wird ignoriert, es const
treten jedoch weiterhin Optimierungen auf.