Sind Pointer als Funktionsparameter schneller als Objekte?
-
Oh ja ok damit hab ich was übersehen...
Danke!
-
Referenzen müssen auch dereferenziert werden, da es eben auch nur stumpfe Pointer sind. Nur beim Compilieren haben sie andere Bedingungen.
-
Vielleicht mal den Assembler-Output anschauen und vergleichen was passiert. Mit entsprechender Optimierung wird da wahrscheinlich sogar in allen 3 Fällen (also ref, zeiger, by-value) das gleiche rauskommen, sprich er optimiert den Aufruf komplett weg. Nicht so viele Gedanken über Performance machen, Rechenzeit verbrät man typischerweise eher an schlechten Algorithmen/Design.
-
l'abra d'or schrieb:
referenzen sind schneller als pointer, denn der pointer muss erst dereferenziert werden, dieser schritt entfällt bei referenzen.
allerdings bin ich mir sicher, dass es bei dir andere performance-bremsen gibt.Eine dereferenzierung ist keine Performancebremse. Nie.
-
Beide Varianten sind genauso schnell. Besonders die erste
.
-
l'abra d'or schrieb:
referenzen sind schneller als pointer, denn der pointer muss erst dereferenziert werden, dieser schritt entfällt bei referenzen.
Nein, wie Fellhuhn schon erwähnt hat, ist die Dereferenzierung lediglich versteckt und nicht explizit. Oder mit welcher Magie kann man die Dereferenzierung in einer Funktion (ohne Inlining) einsparen, ohne eine Kopie anzulegen?
l'abra d'or schrieb:
allerdings bin ich mir sicher, dass es bei dir andere performance-bremsen gibt.
[...]
Versuch erstmal korrekten Code zu schreiben, danach kannst du dich mit diversen Perfomance-Sachen beschäftigen.Hier hingegen kann ich zustimmen. Ich denke, mit der Faustregel, Klassentypen als Referenz auf
const
und skalare Typen wieint
,double*
,enum
-Typen etc. als Wert zu übergeben, fährt man gar nicht so schlecht.Kontrasubjekt schrieb:
Eine dereferenzierung ist keine Performancebremse. Nie.
Sag niemals nie. :p
Natürlich wird es in den allermeisten Fällen nichts ausmachen. Doch vielleicht begegnet einem ja mal ein Fall, wo der Overhead (z.B. bei
const int&
) wirklich nicht vertretbar ist. Und dann ist es doch nicht schlecht, man weiss über solche Dinge Bescheid.Mit Templates kann man sich im Übrigen eine Typ-Metafunktion schreiben, welche für einen Typ den im Allgemeinen schnellsten Parametertypen bestimmt (z.B. Kopie bei skalaren Typen, Const-Referenz sonst).
-
Nexus schrieb:
Natürlich wird es in den allermeisten Fällen nichts ausmachen. Doch vielleicht begegnet einem ja mal ein Fall, wo der Overhead (z.B. bei
const int&
) wirklich nicht vertretbar ist. Und dann ist es doch nicht schlecht, man weiss über solche Dinge Bescheid.Wissen ist nicht schlecht, aber dass es so einen Fall vielleicht geben könnte kauf ich dir nicht ab.
-
Nexus schrieb:
l'abra d'or schrieb:
referenzen sind schneller als pointer, denn der pointer muss erst dereferenziert werden, dieser schritt entfällt bei referenzen.
Nein, wie Fellhuhn schon erwähnt hat, ist die Dereferenzierung lediglich versteckt und nicht explizit. Oder mit welcher Magie kann man die Dereferenzierung in einer Funktion (ohne Inlining) einsparen, ohne eine Kopie anzulegen?
Jut, danke, dann weiß ich wessen Aussagen ich in Zukunft vertrauen kann
Das war nämlich mal ein weiser Rat eines Bekannten, dass ich doch lieber Referenzen übergeben soll als Pointer...
-
l'abra d'or schrieb:
referenzen sind schneller als pointer, denn der pointer muss erst dereferenziert werden, dieser schritt entfällt bei referenzen.
Quatsch! Die Dereferenzierung bei Referenzen ist nur implizit, findest also statt -- auch wenn man dafür nichts schreiben muss (wie zB ein Sternchen). In manchen Fällen kann das wegoptimiert werden, aber das ist nicht die Regel.
Nachtrag: Sehe gerade, dass das schon mehrfach erwähnt worden ist.
Wenn es um die Wahl "const T&" vs "const T" geht, wird die erste Form üblicherweise bei Klassen-Typen verwendet (mit Ausnahme von Iteratoren) und die zweite sonst.
Ein bischen genauer: "const T&" lohnt sich nur, falls "sizeof(T) groß ist" oder "T nicht trivial kopierbar" ist. Für die Typkategorie "trivial kopierbar" findet man im Entwurf des neuen Standards eine Definition. Alle skalaren Objekt-Typen sind trivial kopierbar. Structs und Classes, die nur trivial-kopierbare Elemente und keine benutzerdefinierten Kopier-Konstruktoren und Destruktoren besitzen, sind auch trivial kopierbar (wenn ich mich richtig erinnere). Das heißt, der Compiler darf die Objekte byte-weise kopieren und auf den Stack legen, ohne eine Funktion dafür aufrufen zumüssen. Iteratoren sind typischerweise klein und trivial kopierbar.
Typen, die nicht trivial kopierbar sind und die per "value" übergeben werden, werden typischerweise hinter dem Vorhang tatsächlich auch per Referenz übergeben. Der GCC macht das zumindest so:
void foo(string blah) { }
wird in Wirklichkeit zu
void foo(string* blah) { }
Nur die Adresse des string-Objektes wird übergeben und der Aufrufer kopiert das string-Objekt ggf lokal auf seinem Stack -- oder auch nicht, wenn es ein rvalue war (copy elision). Wie gesagt, das ist das, was der GCC hier macht und eventuell nicht repräsentativ. Man kann sich also mit einer ref-auf-const eine Kopie sparen. Das Dereferenzieren hätte man bei pass-by-value aber auch.
Die "const T&" vs "const T"-Frage wird auch in Scott Meyer's "Effective C++" (3. Ausgabe) diskutiert -- sollte man man gelesen haben.
-
Hi,
also ich bin in dem Thema nicht gut drin, hätte aber erwartet, dass Folgendes bei Referenzen geht:
int i; int& ri = i; ri = 3; int* pi = &i; *pi = 3;
(extrem vereinfachter pseudo ASM Code) [DATEN-SEG] 0x0000 <Platz für i> 0x0100 <Platz für pi> [CODE-SEG] 3 -> 0x0000; // ri = 3; 0x0000 -> 0x0100; // pi = &i; 3 -> DEREF(0x0100); // *pi = 3;
Natürlich kann ein Optimierer in diversen Fällen gleich sehen, dass in 0x0100 ja nur 0x0000 drinstehen kann und es deshalb vereinfachen ... aber das ist für mich eher ein Speziallfall als die Definition "Compiler ersetzt auftreten der Referenz durch das Objekt selber"...
Wie gesagt. Nur meine laienhafte Vorstellung und ich bin überzeugt, dass in der Praxis
- der Optimierer des Compilers an so einer Stelle viel mehr herausholen kann als ein laienhafter Programmierer (mit der Frage "Pointer vs. Referenz") und
- derartige Fragestellungen nicht wirklich relevant sind.Gruß,
Simon2.
-
@Simon2: Ist das denn das Ergebnis eines optimierenden Compilers?
Falls ja: Eine Referenz entspricht ja auch eher einem konstanten Pointer, das kann ein Compiler dann sicherlich besser wegoptimieren.
-
krümelkacker schrieb:
Wenn es um die Wahl "const T&" vs "const T" geht, wird die erste Form üblicherweise bei Klassen-Typen verwendet (mit Ausnahme von Iteratoren) und die zweite sonst.
Du meinst wohl const T& vs. T.
Top-Level-CV-Qualifizierer bei Parametern machen keinen Sinn, weil sie keine Information besitzen, welche für die Schnittstelle relevant sein könnte. Es geht sogar so weit, dass Funktionsdeklarationen ohne jene Qualifizierer als gleich angesehen werden.
krümelkacker schrieb:
Typen, die nicht trivial kopierbar sind und die per "value" übergeben werden, werden typischerweise hinter dem Vorhang tatsächlich auch per Referenz übergeben. Der GCC macht das zumindest so:
Es wäre mir neu, wenn die Kopie eines Value-Parameters vom Compiler wegoptimiert werden dürfte. Der C++-Standard erlaubt Copy Elision eigentlich nur in zwei Fällen: Rückgabewerte von Funktionen und Initialisierung mit temporären Objekten.
Abgesehen davon solltest du auch bedenken, dass es der Funktion erlaubt ist, die Kopie nach Belieben zu ändern. In all diesen Fällen könnte keine Referenz verwendet werden. Besonders bei eigenen Klassen müsste vom Compiler relativ viel Aufwand betrieben werden, abzuschätzen, ob schreibend auf eine Instanz zugegriffen würde.
-
Natürlich wird es Fälle geben, wo ein Stück Code langsamer wird, wenn man Parameter als Pointer/Referenz übergibt, anstatt Kopien zu übergeben.
Stichwort: Aliasing.
Wird eine Kopie als Parameter übergeben, kann der Compiler sicher sein, dass es keine Zeiger gibt, die auf die Kopie zeigen -- sofern in der Funktion nicht selbst Zeiger auf die Kopie erzeugt werden. Dadurch kann besser optimiert werden.Wird sich vermutlich nicht um wahnsinnig viel reissen, aber etliche Prozent sind in extremen Fällen sicherlich drin.
-
Nexus schrieb:
Du meinst wohl const T& vs. T.
Nö. Das war mir schon ernst mit "const T".
Nexus schrieb:
Top-Level-CV-Qualifizierer bei Parametern machen keinen Sinn,
Für denjenigen, der solche Funktionen nutzt, ist es egal. Das const ist trotzdem erlaubt und hindert Dich bei der Implementierung daran, den Parameter zu ändern. Es macht also schon Sinn. Guck mal nach, wie boost::call_traits definiert ist. call_traits<T>::parameter_type ist entweder "const T" oder "const T&".
Nexus schrieb:
krümelkacker schrieb:
Typen, die nicht trivial kopierbar sind und die per "value" übergeben werden, werden typischerweise hinter dem Vorhang tatsächlich auch per Referenz übergeben. Der GCC macht das zumindest so:
Es wäre mir neu, wenn die Kopie eines Value-Parameters vom Compiler wegoptimiert werden dürfte. Der C++-Standard erlaubt Copy Elision eigentlich nur in zwei Fällen: Rückgabewerte von Funktionen und Initialisierung mit temporären Objekten.
Genau, und der letzte Fall ist auch der, der bei der Parameterübergabe andwendbar ist.
Nexus schrieb:
Abgesehen davon solltest du auch bedenken, dass es der Funktion erlaubt ist, die Kopie nach Belieben zu ändern.
nicht, wenn Du "const T" schreibst. :p Die Vorzüge der nicht-const-per-Wert-Übergabe sind mir allerdings auch bekannt. Deswegen hatte ich geschrieben "const T&" versus "const T".
Nexus schrieb:
In all diesen Fällen könnte keine Referenz verwendet werden. Besonders bei eigenen Klassen müsste vom Compiler relativ viel Aufwand betrieben werden, abzuschätzen, ob schreibend auf eine Instanz zugegriffen würde.
Nein, so funktioniert das nicht. Wenn es sich um nicht trivial kopierbare Parameter/Return Typen handelt, werden beim GCC Zeiger übergeben. Das wird nicht vom Standard erzwungen, ist aber eine Implementierungsmöglichkeit, die genutzt werden kann, um intensiv "copy elisions" durchführen zu können. Der Trick dabei ist der, dass der Aufrufer einer Funktion entsprechend eine Kopie (falls nötig) eines pass-by-value Parameters anfertigt bzw Platz für das Rückgabebjekt schafft und die Adresse an die Funktion übergibt. Beispiel:
string quelle(); void senke(string); int main() { string s = quelle(); senke(s); senke(quelle()); }
Das wird dann in etwa so übersetzt (Low-Level Pseudo-Code)
void quelle(string* wohin); // führt quasi placement-new aus void senke(string* param); int main() { noinit string s; // keine Initialisierung quelle(&s); // quasi-placement-new in der Funktion, // s ist nun initialisiert. { string temp = s; // <-- lokale Kopie da pass-by-value senke(&temp); } { noinit string t; // keine Initialisierung quelle(&t); // quelle() liefert ein RValue, // t ist jetzt initialisiert. senke(&t); // <-- copy elision, keine extra Kopie // senke darf das Objekt verändern und // main() interessiert das nicht die Bohne } }
I rest my case
-
krümelkacker schrieb:
Für denjenigen, der solche Funktionen nutzt, ist es egal.
Ich bin zumindest kurz verwirrt, wenn ich in die Deklaration anschaue, dort ein
const
sehe und dann merke, dass es sich doch um einen Value-Parameter handelt (normalerweise assoziiere ichconst
in Parameterlisten mit Zeigern/Referenzen auf konstante Objekte). Da es für den Aufrufer auch keinen Unterschied macht, hat dasconst
meiner Meinung nach nichts in der Schnittstelle zu suchen.krümelkacker schrieb:
Das const ist trotzdem erlaubt und hindert Dich bei der Implementierung daran, den Parameter zu ändern. Es macht also schon Sinn.
Das ist mir bewusst. Und genau hier liegt mein Problem: Ich verstehe nicht ganz, weshalb der Funktion verboten wird, die Kopie zu verändern. Das kann der Aussenwelt ja egal sein. Ich meine, sie kann trotzdem immer noch beliebig viele Kopien anfertigen, aber auf diese Weise geht man unnötige Nachteile ein (mehr Code, schlechtere Performance).
Mir ist auch nicht ganz klar, inwiefern
const
-qualifizierte Value-Parameter von Vorteil für die Optimierung sein sollen. Bei temporären Objekten spielt es keine Rolle, ob das Argument verändert wird, und ansonsten muss sowieso eine Kopie angefertigt werden, da wäreconst
nur einschränkend. Oder übersehe ich etwas?krümelkacker schrieb:
Genau, und der letzte Fall ist auch der, der bei der Parameterübergabe andwendbar ist.
[...]
Nein, so funktioniert das nicht. Wenn es sich um nicht trivial kopierbare Parameter/Return Typen handelt, werden beim GCC Zeiger übergeben. Das wird nicht vom Standard erzwungen, ist aber eine Implementierungsmöglichkeit, die genutzt werden kann, um intensiv "copy elisions" durchführen zu können. Der Trick dabei ist der, dass der Aufrufer einer Funktion entsprechend eine Kopie (falls nötig) eines pass-by-value Parameters anfertigt bzw Platz für das Rückgabebjekt schafft und die Adresse an die Funktion übergibt.Okay, so ist es klar. Ich habe dich falsch verstanden (nämlich so, dass die besagte Regel für alle Argumente gelte und nicht nur für Temporaries). Wenn der G++ bei nicht-temporären Objekten schön eine Kopie anlegt, ist ja alles in Ordnung. Vielen Dank für die ausführliche Erklärung und das Codebeispiel!
Edit: Der letzte Absatz bezieht sich nur auf die Notwendigkeit zu kopieren. Die oberen Fragen sind immer noch offen.
-
Nexus schrieb:
krümelkacker schrieb:
Für denjenigen, der solche Funktionen nutzt, ist es egal.
Ich bin zumindest kurz verwirrt, wenn ich in die Deklaration anschaue, dort ein
const
sehe und dann merke, dass es sich doch um einen Value-Parameter handelt (normalerweise assoziiere ichconst
in Parameterlisten mit Zeigern/Referenzen auf konstante Objekte).Da es für den Aufrufer auch keinen Unterschied macht, hat das
const
meiner Meinung nach nichts in der Schnittstelle zu suchen.Ich habe nicht gesagt, dass es für DeKLARAtion auch sinnvoll ist oder überhaupt irgend einen Effekt hat.
Nexus schrieb:
krümelkacker schrieb:
Das const ist trotzdem erlaubt und hindert Dich bei der Implementierung daran, den Parameter zu ändern. Es macht also schon Sinn.
Das ist mir bewusst. Und genau hier liegt mein Problem: Ich verstehe nicht ganz, weshalb der Funktion verboten wird, die Kopie zu verändern.
Wenn Du es verändern willst, stellt sich die Frage nach "const T&" vs "const T" einfach nicht. Natürlich gibt es Fälle, in denen Du nichts verändern musst und auch keine Kopie erstellen musst. Dann stellt sich die Frage "Referenz oder nicht?". Und ein const auch ohne Referenz wäre primär dazu da, Programmierfehler zu reduzieren. Wenn Du nicht vorhast, es zu verändern, und Du const benutzt, dann macht der Compiler dich schon eher darauf aufmerksam, wenn Du versehentlich etwas machst, was Du eigentlich nicht wolltest.
-
SeppJ schrieb:
@Simon2: Ist das denn das Ergebnis eines optimierenden Compilers?
...Gemeint war es eben als NICHT-optimierter Code!
Hintergrund ist:
- Ein Optimierer lässt aufgrund einer (vom Hersteller ausgedachten) Heuristik bestimmte Operationen/Optionen weg, die aber im Standard prinzipiell be-/vorgeschrieben sind.
- Ich habe ein Verhalten vor Augen, das bereits der Standard vorschreibt.
Ist zwar nur ein feiner Unterschied, aber bisweilen sind auch die interessant.
Mir kräuseln sich halt bei solchen Pauschalaussagen (auch, wenn sie eigentlich nicht als solche gemeint sind) wie "Referenzen sind auch nur Zeiger" die Fußnägel hoch, weil sich diese "Spezialfallbeschreibung" schnell zur "Eselbrücke" und bald auch zum "Lehrsatz" entwickeln.
Und in C++ werden Referenzen in vielen Zusammenhängen ganz anders behandelt als Zeiger. Und zwar in einer Art und Weise, dass ein Compiler bereits wissen kann, was er weglassen darf und nicht "raten" muss.Aber natürlich: Reale (und erst recht ideale) Optimierer werden in zahllosen Fällen zum selben Ergebnis im Binary führen...
Gruß,
Simon2.
-
krümelkacker schrieb:
Ich habe nicht gesagt, dass es für DeKLARAtion auch sinnvoll ist oder überhaupt irgend einen Effekt hat.
Gut, dann sind wir uns ja einig.
Ich dachte eben, du würdest dich generell für
const
bei Wert-Parametern aussprechen. In der Definition kann man das machen, halt wie man sonstige Konstanten deklariert. Ich persönlich fänds aber übertrieben, alle Parameter und lokalen Objekte, welche nicht verändert werden, alsconst
zu deklarieren. Ich heb mirconst
lieber für die "wichtigen" Dinge auf. Aber das geht dann Richtung Codestil...