Gibt const-Korrektheit dem Compiler mehr Raum für Optimierungen?

77

Ich weiß, dass es die Lesbarkeit verbessert und das Programm weniger fehleranfällig macht, aber um wie viel verbessert es die Leistung?

Und was ist der Hauptunterschied zwischen einer Referenz und einem constZeiger? Ich würde annehmen, dass sie anders im Speicher gespeichert sind, aber wie?

slartibartfast
quelle
8
Nein, aber es kann Ihrem eigenen Code Raum für mehr Optimierung geben. Ein Beispiel ist eine unveränderliche Zeichenfolgenklasse, die Teilzeichenfolgenoperationen mit konstanter Zeit und die Erstellung aus Literalen ohne dynamische Zuordnung bereitstellt. Darin steckt viel Kraft const, aber hauptsächlich für Menschen und den Code, den wir schreiben. Der Compiler kann nichts vertrauen und kann daher nicht in gleicher Weise davon profitieren.
Prost und hth. - Alf

Antworten:

70

[Bearbeiten: OK, diese Frage ist subtiler als ich zuerst dachte.]

Das Deklarieren eines Zeigers auf const oder einer Referenz von const hilft keinem Compiler, etwas zu optimieren. (Siehe auch das Update am Ende dieser Antwort.)

Die constErklärung gibt nur an, wie ein Bezeichner im Rahmen seiner Erklärung verwendet wird. Es heißt nicht, dass sich das zugrunde liegende Objekt nicht ändern kann.

Beispiel:

int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}

Der Compiler kann nicht davon ausgehen, dass *pdies durch den Aufruf von nicht geändert wird bar(), da pdies (z. B.) ein Zeiger auf ein globales int sein und es bar()möglicherweise ändern kann.

Wenn der Compiler genug über den Aufrufer von weiß foo()und der Inhalt, von bar()dem er beweisen kann, dass er bar()sich nicht ändert *p, kann er diesen Beweis auch ohne die const-Deklaration ausführen .

Dies gilt jedoch im Allgemeinen. Da sich dies constnur im Bereich der Deklaration auswirkt, kann der Compiler bereits sehen, wie Sie den Zeiger oder die Referenz in diesem Bereich behandeln. Es ist bereits bekannt, dass Sie das zugrunde liegende Objekt nicht ändern.

Kurz gesagt, alle const in diesem Zusammenhang tun, ist zu verhindern, dass Sie Fehler machen. Es sagt dem Compiler nichts, was er noch nicht weiß, und ist daher für die Optimierung irrelevant.

Was ist mit Funktionen, die aufrufen foo()? Mögen:

int x = 37;
foo(&x);
printf("%d\n", x);

Kann der Compiler beweisen, dass dies 37 druckt, da foo()ein const int *?

Nein. Auch wenn foo()ein Zeiger auf const benötigt wird, kann dies die Konstanz wegwerfen und das int ändern. (Dies ist kein undefiniertes Verhalten.) Auch hier kann der Compiler im Allgemeinen keine Annahmen treffen. und wenn es genug weiß, um foo()eine solche Optimierung vorzunehmen, wird es das auch ohne die wissen const.

Die einzige Zeit const, in der Optimierungen möglich sind, sind folgende Fälle:

const int x = 37;
foo(&x);
printf("%d\n", x);

Hier bedeutet das Modifizieren xdurch irgendeinen Mechanismus (z. B. indem man einen Zeiger darauf nimmt und den wegwirft const), dass man undefiniertes Verhalten aufruft. Der Compiler kann also davon ausgehen, dass Sie dies nicht tun, und er kann die Konstante 37 in printf () übertragen. Diese Art der Optimierung ist für jedes von Ihnen deklarierte Objekt zulässig const. (In der Praxis ist eine lokale Variable, auf die Sie niemals verweisen, nicht von Vorteil, da der Compiler bereits sehen kann, ob Sie sie in seinem Umfang ändern.)

Um Ihre "Randnotiz" -Frage zu beantworten, (a) ist ein const-Zeiger ein Zeiger; und (b) ein konstanter Zeiger kann gleich NULL sein. Sie haben Recht, dass die interne Darstellung (dh eine Adresse) höchstwahrscheinlich dieselbe ist.

[aktualisieren]

Wie Christoph in den Kommentaren betont, ist meine Antwort unvollständig, weil sie nicht erwähnt wirdrestrict .

In Abschnitt 6.7.3.1 (4) der C99-Norm heißt es:

Während jeder Ausführung von B sei L ein beliebiger l-Wert, dessen & L auf P basiert. Wenn L verwendet wird, um auf den Wert des von ihm bezeichneten Objekts X zuzugreifen, und X ebenfalls (auf irgendeine Weise) geändert wird, gelten die folgenden Anforderungen : T darf nicht qualifiziert sein. ...

(Hier ist B ein Basisblock, über den sich P, ein Beschränkungszeiger auf T, im Geltungsbereich befindet.)

Wenn also eine C-Funktion foo()wie folgt deklariert ist:

foo(const int * restrict p)

... dann der Compiler kann davon ausgehen , dass keine Änderungen *pwährend der Laufzeit auftreten p- dh während der Ausführung foo()- weil sonst das Verhalten nicht definiert wäre.

Also im Prinzip kombinieren restrict mit einem Zeiger auf const beide Optimierungen ermöglichen, die oben verworfen wurden. Implementieren Compiler tatsächlich eine solche Optimierung, frage ich mich? (GCC 4.5.2 zumindest nicht.)

Beachten Sie, dass restrictnur in C vorhanden ist, nicht in C ++ (nicht einmal in C ++ 0x), außer als compilerspezifische Erweiterung.

Nemo
quelle
10
"Also im Fall von globalen Variablen - die Sie nicht verwenden sollten" Entschuldigung, aber das ist ein schlechter Rat - es gibt viele Fälle, in denen globale Variablen verwendet werden können und sollten.
Adam Rosenfield
4
Das Ändern einer const Variablen über const_castruft UB auf, nicht nur globale const...
ildjarn
1
@Billy: Die Antwort mag in Bezug auf einfache Zeiger korrekt sein const, ist aber zumindest unvollständig - Nemo hat vergessen restrict: zu deklarieren, foo()wie int foo(const int *restrict p)dies dem Compiler tatsächlich sagen würde, dass der Wert, auf den von pzeigt, durch einen Aufruf nicht geändert wird vonfoo()
Christoph
6
@Billy: C99-Abschnitt 6.7.3.1 erneut lesen: Was restricttatsächlich bedeutet, ist, dass (1) wenn ein Objekt, auf das ein restrictqualifizierter Zeiger zeigt, geändert wird, der gesamte Zugriff (einschließlich des Objekts , das die Änderung ausgelöst hat) darauf basieren muss Zeiger und (2) - der Teil, der hier relevant ist - wenn eine Änderung constauftritt, darf der Zeiger nicht qualifiziert werden ; Fazit: Es ist illegal, Objekte zu ändern, auf die durch restrictqualifizierte Zeiger constwährend der Lebensdauer dieses Zeigers verwiesen wird
Christoph
1
@AnishRamaswamy: Ich bezweifle nicht, dass "einschränken" in einigen Fällen einen Unterschied macht, da es bei der Alias-Analyse hilft ... Was es nicht tut, ist diese spezielle Optimierung der Weitergabe der Konstante über den Aufruf zu ermöglichen. Ich weiß es, weil ich es ausprobiert und mir den Assembler-Code angesehen habe. (Und ich habe es gerade noch einmal mit GCC 4.8.1 versucht; gleiches Ergebnis.)
Nemo
6

constIn C ++ gibt es zwei Probleme (was die Optimierung betrifft):

  • const_cast
  • mutable

const_cast Dies bedeutet, dass die Funktion die Konstanz möglicherweise wegwirft und das Objekt ändert, obwohl Sie ein Objekt als Konstantenreferenz oder Konstantenzeiger übergeben (zulässig, wenn das Objekt zunächst keine Konstante ist).

mutableDies bedeutet, dass consteinige Teile eines Objekts möglicherweise geändert werden (Caching-Verhalten). Objekte, auf die verwiesen wird (anstatt Eigentum zu sein), können in constMethoden geändert werden, selbst wenn sie logisch Teil des Objektstatus sind. Und schließlich können auch globale Variablen geändert werden ...

const ist hier, um dem Entwickler zu helfen, logische Fehler frühzeitig zu erkennen.

Matthieu M.
quelle
Wenn eine Funktion const mit verwendet const_castund das Objekt dann ändert, ist dies ein undefiniertes Verhalten. Compiler können davon ausgehen, dass dieser Fall niemals auftritt.
James Picone
1
@ JamesPicone: Ja und nein. Es ist ein undefiniertes Verhalten, ein constObjekt zu ändern , aber es ist der Grund, const_castdas constAttribut einer Referenz oder eines Zeigers wegzuwerfen , um ein Nicht- constObjekt zu ändern . Der Compiler kann also einige Leistungssteigerungen erzielen const, nur einen Bruchteil dessen, worauf er verzichten könnte const_cast.
Matthieu M.
6

Ich kann mir zwei Fälle constvorstellen, in denen eine ordnungsgemäße Qualifizierung zusätzliche Optimierungen ermöglicht (in Fällen, in denen keine Analyse des gesamten Programms verfügbar ist):

const int foo = 42;
bar(&foo);
printf("%i", foo);

Hier kann der Compiler drucken, 42ohne den Hauptteil von bar()(der in der aktuellen Übersetzungseinheit möglicherweise nicht sichtbar ist) untersuchen zu müssen, da alle Änderungen foounzulässig sind ( dies entspricht dem Beispiel von Nemo ).

Dies ist jedoch auch ohne Kennzeichnung fooals constdurch Deklaration bar()als möglich

extern void bar(const int *restrict p);

In vielen Fällen möchte der Programmierer tatsächlich restrictqualifizierte Zeiger auf constund keine einfachen Zeiger auf constals Funktionsparameter, da nur die ersteren Garantien für die Veränderbarkeit der Objekte geben, auf die verwiesen wird.

Zum zweiten Teil Ihrer Frage: Für alle praktischen Zwecke kann eine C ++ - Referenz als konstanter Zeiger (kein Zeiger auf einen konstanten Wert!) Mit automatischer Indirektion betrachtet werden - sie ist nicht "sicherer" oder "schneller". als ein Zeiger, nur bequemer.

Christoph
quelle
Der Compiler kann den Wert eines Nicht-Konstanten-Objekts weitergeben, wenn es als const-Referenz übergeben wird: Aufgrund von §5.2.2 / 5 kann er stillschweigend eine temporäre Kopie des übergebenen Objekts erstellen.
Ruslan
4

Konst-Korrektheit hilft im Allgemeinen nicht der Leistung; Die meisten Compiler machen sich nicht einmal die Mühe, die Konstanz über das Frontend hinaus zu verfolgen. Das Markieren von Variablen als const kann je nach Situation hilfreich sein.

Referenzen und Zeiger werden genauso im Speicher gespeichert.

servn
quelle
2
Können Sie ein Beispiel geben, wo es hilft (oder sollte)? Das Sagen von "hilft nicht", "kann helfen" und "je nach Situation" im selben Absatz verdeutlicht die Situation nicht wirklich.
André Caron
const hilft bei Konstrukten wie den folgenden (und komplizierteren Varianten): int x = 10; int f () {return x; } vs. const int x = 10; int f () {return x; }
servn
2

Dies hängt wirklich vom Compiler / der Plattform ab (es kann bei einigen noch nicht geschriebenen Compilern oder auf einer Plattform, die Sie nie verwenden, zur Optimierung beitragen). Die C- und C ++ - Standards sagen nichts über die Leistung aus, außer dass für einige Funktionen Komplexitätsanforderungen gestellt werden.

Zeiger und Verweise auf const helfen normalerweise nicht bei der Optimierung, da die const-Qualifikation in einigen Situationen legal weggeworfen werden kann und es möglich ist, dass das Objekt durch eine andere Nicht-const-Referenz geändert werden kann. Andererseits kann es hilfreich sein, ein Objekt als const zu deklarieren, da es garantiert, dass das Objekt nicht geändert werden kann (selbst wenn es an Funktionen übergeben wird, deren Definition der Compiler nicht kennt). Auf diese Weise kann der Compiler das const-Objekt im Nur-Lese-Speicher speichern oder seinen Wert an einem zentralen Ort zwischenspeichern, wodurch der Bedarf an Kopien und Überprüfungen auf Änderungen verringert wird.

Zeiger und Referenzen werden normalerweise genauso implementiert, aber auch dies ist völlig plattformabhängig. Wenn Sie wirklich interessiert sind, sollten Sie sich den generierten Maschinencode für Ihre Plattform und Ihren Compiler in Ihrem Programm ansehen (wenn Sie tatsächlich einen Compiler verwenden, der Maschinencode generiert).

Mankarse
quelle
1

Eine Sache ist, wenn Sie eine globale Variable const deklarieren, ist es möglicherweise möglich, sie in den schreibgeschützten Teil einer Bibliothek oder ausführbaren Datei zu legen und sie somit mit einer schreibgeschützten mmap für mehrere Prozesse freizugeben. Dies kann unter Linux ein großer Speichergewinn sein, zumindest wenn viele Daten in globalen Variablen deklariert sind.

In einer anderen Situation kann der Compiler, wenn Sie eine konstante globale Ganzzahl oder float oder enum deklarieren, die Konstante möglicherweise nur inline setzen, anstatt eine Variablenreferenz zu verwenden. Ich glaube, das ist ein bisschen schneller, obwohl ich kein Compiler-Experte bin.

Referenzen sind nur Hinweise darauf, was die Implementierung betrifft.

Havoc P.
quelle
Gleiches Angebot wie die anderen Antworten, die ich erhalten habe - wenn es wirklich nie im gesamten Programm geschrieben wurde, kann der Compiler diese Art von Optimierungen durchführen, unabhängig davon, ob es markiert ist constoder nicht.
Billy ONeal
1
@ Billy: nicht für externVariablen.
Matthieu M.
@Matthieu: Er bezieht sich speziell auf die Optimierung des gesamten Programms, die zur Verbindungszeit durchgeführt wird, wenn externStopps viel bedeuten.
Dennis Zickefoose
@ Tennis: Ah richtig, sorry, ich bin es gewohnt, mit DLLs umzugehen, bei denen WPO viel Kraft verliert.
Matthieu M.
@Billy Vielleicht theoretisch, aber zumindest in der Praxis unter Linux werden Sie gemeinsam genutzte Bibliotheken verwenden (oder eine gemeinsam genutzte Bibliothek schreiben), und der Compiler konnte dies nicht tun. Welche Art von Programm unter Linux verwendet keine gemeinsam genutzten Bibliotheken? Grundsätzlich keiner von ihnen. Ich bin mir nicht sicher, ob gcc dies auf jeden Fall tut, gemeinsam genutzte Bibliotheken oder nein - wenn ja, ist es eine ziemlich neue Funktion.
Havoc P
0

Dies kann die Leistung ein wenig verbessern, jedoch nur, wenn Sie über die Deklaration direkt auf das Objekt zugreifen. Referenzparameter und dergleichen können nicht optimiert werden, da es möglicherweise andere Pfade zu einem Objekt gibt, das ursprünglich nicht als const deklariert wurde, und der Compiler im Allgemeinen nicht feststellen kann, ob das Objekt, auf das Sie verweisen, tatsächlich als const deklariert wurde oder nicht, es sei denn, dies ist die von Ihnen verwendete Deklaration.

Wenn Sie eine const-Deklaration verwenden, weiß der Compiler, dass extern kompilierte Funktionskörper usw. diese nicht ändern können, sodass Sie dort einen Vorteil erhalten. Und natürlich werden Dinge wie const int's zur Kompilierungszeit weitergegeben, das ist also ein großer Gewinn (im Vergleich zu nur einem int).

Referenzen und Zeiger werden genau gleich gespeichert, sie verhalten sich nur syntaktisch unterschiedlich. Referenzen sind im Grunde genommen Umbenennungen und daher relativ sicher, während Zeiger auf viele verschiedene Dinge verweisen können und daher leistungsfähiger und fehleranfälliger sind.

Ich denke, der const-Zeiger wäre architektonisch identisch mit der Referenz, also wären der Maschinencode und die Effizienz gleich. Der eigentliche Unterschied liegt in der Syntax. Referenzen sind eine sauberere und leichter zu lesende Syntax. Da Sie die zusätzliche Maschinerie eines Zeigers nicht benötigen, wird eine Referenz stilistisch bevorzugt.

d00t
quelle
Wenn das Element nicht geändert wird, werden anständige Compiler heutzutage wissen, dass es in constOrdnung ist, ohne dass Sie es sagen. (Apropos aktuelle MSVC ++ und GCCs, beide haben Link Time Code Generierung)
Billy ONeal
1
@Billy: Optimierungen der Verbindungszeit sind kein Allheilmittel, da alle Bibliotheken auch mit aktiviertem lto kompiliert werden müssten, ganz zu schweigen von gemeinsam genutzten Bibliotheken - der Compiler kann nicht über Code nachdenken, den er nie sehen wird ...
Christoph
@Christoph: Der Compiler kann der constQualifikation von Variablen, die er auch nicht sehen kann, niemals vertrauen . Dieser Speicher kann durch völlig andere Sprachen, in denen er constnicht existiert, durcheinander gebracht werden , oder er kann sogar in einem Raw-Assembler geschrieben werden. In solchen Fällen, in denen eine constVariable dieser Bibliothek zugänglich gemacht wird, muss der Compiler die const-Qualifikation ignorieren. Daher sehe ich nicht, wie relevant das für diese Frage ist.
Billy ONeal