Ich studiere gerade, wie man effizienten C ++ - Code schreibt, und bei Funktionsaufrufen kommt mir eine Frage in den Sinn. Vergleich dieser Pseudocode-Funktion:
not-void function-name () {
do-something
return value;
}
int main () {
...
arg = function-name();
...
}
mit dieser ansonsten identischen Pseudocode-Funktion:
void function-name (not-void& arg) {
do-something
arg = value;
}
int main () {
...
function-name(arg);
...
}
Welche Version ist effizienter und in welcher Hinsicht (Zeit, Speicher usw.)? Wenn es darauf ankommt, wann wäre der erste effizienter und wann wäre der zweite effizienter?
Bearbeiten : Für den Kontext beschränkt sich diese Frage auf Unterschiede zwischen Hardwareplattformen und größtenteils auch auf Software. Gibt es maschinenunabhängige Leistungsunterschiede?
Bearbeiten : Ich sehe nicht, wie dies ein Duplikat ist. Die andere Frage besteht darin, die Übergabe als Referenz (vorheriger Code) mit der Übergabe als Wert (unten) zu vergleichen:
not-void function-name (not-void arg)
Welches ist nicht das gleiche wie meine Frage. Ich konzentriere mich nicht darauf, wie ich ein Argument besser an eine Funktion übergeben kann. Mein Fokus liegt darauf, wie ich ein Ergebnis besser an eine Variable von außerhalb des Bereichs weitergeben kann.
quelle
Antworten:
Berücksichtigen Sie zunächst, dass die Rückgabe eines Objekts immer lesbarer (und in der Leistung sehr ähnlich) ist als die Übergabe als Referenz. Daher könnte es für Ihr Projekt interessanter sein, das Objekt zurückzugeben und die Lesbarkeit zu verbessern, ohne wichtige Leistungsunterschiede zu haben . Wenn Sie wissen möchten, wie Sie die niedrigsten Kosten erzielen können, müssen Sie Folgendes zurückgeben:
Wenn Sie ein einfaches oder einfaches Objekt zurückgeben müssen, ist die Leistung in beiden Fällen ähnlich.
Wenn das Objekt so groß und komplex ist, würde für die Rückgabe eine Kopie benötigt, und es könnte langsamer sein, als es als referenzierten Parameter zu haben, aber ich denke, es würde weniger Speicherplatz verbrauchen.
Man muss sowieso denken, dass Compiler viele Optimierungen vornehmen, die beide Leistungen sehr ähnlich machen. Siehe Kopieren von Elision .
quelle
Nun, man muss verstehen, dass das Zusammenstellen kein einfaches Geschäft ist. Es werden viele Überlegungen angestellt, wenn der Compiler Ihren Code kompiliert.
Man kann diese Frage nicht einfach beantworten, da der C ++ - Standard kein Standard-ABI (Abstract Binary Interface) bietet, sodass jeder Compiler den Code nach Belieben kompilieren kann und Sie bei jeder Kompilierung unterschiedliche Ergebnisse erzielen können.
Beispielsweise wird in einigen Projekten C ++ als verwaltete Erweiterung von Microsoft CLR (C ++ / CX) kompiliert. Da alles bereits einen Verweis auf ein Objekt auf dem Heap enthält, gibt es wohl keinen Unterschied.
Die Antwort ist für nicht verwaltete Kompilierungen nicht einfacher. Einige Fragen kommen mir in den Sinn, wenn ich an "Wird XXX schneller laufen als JJJ?" denke, zum Beispiel:
std::array
) oder hat es einen Zeiger auf etwas auf dem Haufen? (zBstd::vector
)?Wenn ich ein konkretes Beispiel gebe, gehe ich davon aus, dass bei MSVC ++ und GCC die Rückgabe
std::vector
nach Wert aufgrund der R-Wert-Optimierung als Referenzübergabe erfolgt und etwas (um einige Nanosekunden) schneller ist als die Rückgabe des Vektors durch bewegen. Dies kann beispielsweise bei Clang völlig anders sein.Letztendlich ist die Profilerstellung hier die einzig wahre Antwort.
quelle
Zurückführen des Objekts sollte wegen eines optimsation genannt in den meisten Fällen verwendet werden Kopie elision .
Abhängig davon, wie Ihre Funktion verwendet werden soll, ist es möglicherweise besser, das Objekt als Referenz zu übergeben.
Schauen Sie sich
std::getline
zum Beispiel an, welches alsstd::string
Referenz dient. Diese Funktion soll als Schleifenbedingung verwendet werden und füllt a so lange,std::string
bis EOF erreicht ist. Durch die Verwendung derselbenstd::string
kann der Speicherplatz desstd::string
in jeder Schleifeniteration wiederverwendet werden, wodurch die Anzahl der durchzuführenden Speicherzuweisungen drastisch reduziert wird.quelle
Einige der Antworten haben dies berührt, aber ich möchte im Lichte der Bearbeitung hervorheben
Wenn dies die Grenzen der Frage sind, lautet die Antwort, dass es keine Antwort gibt. Die c ++ - Spezifikation legt nicht fest, wie entweder die Rückgabe eines Objekts oder die Übergabe einer Referenz leistungsmäßig implementiert wird, sondern nur die Semantik dessen, was beide in Bezug auf Code tun.
Es steht einem Compiler daher frei, einen auf identischen Code wie den anderen zu optimieren, vorausgesetzt, dies erzeugt keinen wahrnehmbaren Unterschied zum Programmierer.
In Anbetracht dessen denke ich, dass es am besten ist, das zu verwenden, was für die Situation am intuitivsten ist. Wenn die Funktion tatsächlich ein Objekt als Ergebnis einer Aufgabe oder Abfrage "zurückgibt", geben Sie es zurück, während Sie, wenn die Funktion eine Operation für ein Objekt ausführt, das dem externen Code gehört, als Referenz übergeben.
Sie können die Leistung hierfür nicht verallgemeinern. Machen Sie zunächst alles Intuitive und sehen Sie, wie gut Ihr Zielsystem und Ihr Compiler es optimieren. Wenn Sie nach der Profilerstellung ein Problem feststellen, ändern Sie es bei Bedarf.
quelle
Wir können nicht 100% allgemein sein, da verschiedene Plattformen unterschiedliche ABIs haben, aber ich denke, wir können einige ziemlich allgemeine Aussagen treffen, die für die meisten Implementierungen gelten, mit der Einschränkung, dass diese Dinge hauptsächlich für Funktionen gelten, die nicht inline sind.
Betrachten wir zunächst primitive Typen. Auf einer niedrigen Ebene wird eine Parameterübergabe als Referenz unter Verwendung eines Zeigers implementiert, während primitive Rückgabewerte typischerweise buchstäblich in Registern übergeben werden. Rückgabewerte erzielen daher wahrscheinlich eine bessere Leistung. Bei einigen Architekturen gilt das Gleiche für kleine Strukturen. Das Kopieren eines Wertes, der klein genug ist, um in ein oder zwei Register zu passen, ist sehr billig.
Betrachten wir nun größere, aber immer noch einfache (keine Standardkonstruktoren, Kopierkonstruktoren usw.) Rückgabewerte. In der Regel werden größere Rückgabewerte behandelt, indem der Funktion ein Zeiger auf die Stelle übergeben wird, an der der Rückgabewert platziert werden soll. Mit Copy Elision können die von der Funktion zurückgegebene Variable, die für die Rückgabe verwendete temporäre Variable und die Variable im Aufrufer, in die das Ergebnis gestellt wird, zu einer zusammengeführt werden. Die Grundlagen der Übergabe wären also für die Übergabe als Referenz und den Rückgabewert ähnlich.
Insgesamt würde ich für primitive Typen erwarten, dass die Rückgabewerte geringfügig besser sind, und für größere, aber immer noch einfache Typen würde ich erwarten, dass sie gleich oder besser sind, es sei denn, Ihr Compiler ist bei der Kopierelision sehr schlecht.
Bei Typen, die Standardkonstruktoren, Kopierkonstruktoren usw. verwenden, werden die Dinge komplexer. Wenn die Funktion mehrmals aufgerufen wird, erzwingen Rückgabewerte, dass das Objekt jedes Mal neu erstellt wird, während Referenzparameter möglicherweise die Wiederverwendung der Datenstruktur ohne Rekonstruktion ermöglichen. Andererseits erzwingen Referenzparameter eine (möglicherweise unnötige) Konstruktion, bevor die Funktion aufgerufen wird.
quelle
Diese Pseudocode-Funktion:
not-void function-name () { do-something return value; }
wäre besser zu verwenden, wenn der zurückgegebene Wert keine weiteren Änderungen erfordert. Der übergebene Parameter wird nur in der geändert
function-name
. Es sind keine weiteren Verweise darauf erforderlich.ansonsten identische Pseudocode-Funktion:
void function-name (not-void& arg) { do-something arg = value; }
Dies wäre nützlich, wenn wir eine andere Methode haben, die den Wert derselben Variablen wie moderiert, und die Änderungen, die durch einen der Aufrufe an der Variablen vorgenommen wurden, beibehalten müssen.
void another-function-name (not-void& arg) { do-something arg = value; }
quelle
In Bezug auf die Leistung sind Kopien im Allgemeinen teurer, obwohl der Unterschied für kleine Objekte vernachlässigbar sein kann. Außerdem kann Ihr Compiler eine Rückgabekopie für eine Verschiebung optimieren, was der Übergabe einer Referenz entspricht.
Ich würde empfehlen, keine Nichtreferenzen weiterzugeben, es
const
sei denn, Sie haben einen guten Grund dazu. Verwenden Sie den Rückgabewert (zB Funktionen dertryGet()
Sortierung).Wenn Sie möchten, können Sie den Unterschied selbst messen, wie andere bereits gesagt haben. Führen Sie den Testcode für beide Versionen einige Millionen Mal aus und sehen Sie den Unterschied.
quelle
const
aufgrund des impliziten Status einer Referenz möglicherweise zu weiteren Problemen führen. Wenn Sie eine Referenz ändern, müssen wir sicherstellen, dass die Zustände anderer Argumente auch nicht durch die Referenz geändert werden.