11

Betrachten wir ein Objekt foo (die eine int, eine double, eine benutzerdefinierte struct, eine class, was auch immer sein kann). Mein Verständnis ist, dass die Übergabe foo durch Verweis auf eine Funktion (oder nur einen Zeiger auf foo) zu höherer Leistung führt, da wir vermeiden, eine lokale Kopie (die teuer sein könnte, wenn foo ist groß).Performance-Kosten der Weitergabe von Wert vs durch Referenz oder Zeiger?

Von der Antwort here scheint es jedoch, dass Zeiger auf einem 64-Bit-System in der Praxis erwartet werden können, eine Größe von 8 Bytes zu haben, unabhängig davon, worauf hingewiesen wird. Auf meinem System ist ein float 4 Bytes. Bedeutet das, dass wenn foo vom Typ float ist, dann ist es effizienter, nur foo nach Wert zu übergeben, anstatt einen Zeiger darauf zu geben (unter der Annahme, keine anderen Einschränkungen, die Verwendung eines effizienter als die andere innerhalb der Funktion machen würde) ?

+4

Sie sollten es messen. Die Größe der Sache, die referenziert/kopiert wird, ist nicht die einzige Sache, die ins Spiel kommt. – juanchopanza

+0

http://stackoverflow.com/questions/21605579/how-true-is-want-speed-pass-by-value –

+0

Kurz gesagt: Es ist fast immer effizienter, native Typen (int, float, double) zu übergeben Wert als durch Referenz. Nicht nur, weil ein Zeiger in den meisten Fällen größer oder so groß wie der native Datentyp ist, sondern auch, weil es für den Optimierer viel schwieriger ist, Referenzparameter als Werteparameter zu optimieren. – MikeMB

Antwort

11

Es hängt davon ab, was Sie mit "Kosten" und Eigenschaften der Host-System (Hardware, Betriebssystem) in Bezug auf Operationen.

Wenn Ihr Kostenmaß die Speichernutzung ist, dann ist die Berechnung der Kosten offensichtlich - summieren Sie die Größen von allem, was kopiert wird.

Wenn Ihr Maß ist Ausführungsgeschwindigkeit (oder "Effizienz"), dann ist das Spiel anders. Hardware (und Betriebssysteme und Compiler) neigen dazu, aufgrund von dedizierten Schaltungen (Maschinenregistern und wie sie verwendet werden) für das Ausführen von Operationen beim Kopieren von Dingen bestimmter Größen optimiert zu sein.

Es ist zum Beispiel üblich, dass eine Maschine eine Architektur (Maschinenregister, Speicherarchitektur usw.) hat, die zu einem "Sweet Spot" führt - das Kopieren von Variablen einer gewissen Größe ist am "effizientesten", aber das Kopieren ist größer ODER kleinere Variablen ist weniger so. Größere Variablen kosten mehr zum Kopieren, da möglicherweise mehrere Kopien kleinerer Blöcke erforderlich sind. Kleinere können auch mehr kosten, da der Compiler den kleineren Wert in eine größere Variable (oder ein größeres Register) kopieren, die Operationen darauf ausführen und den Wert dann zurückkopieren muss.

Beispiele mit Gleitkommazahl enthalten einige Cray Supercomputer, die nativ double precision floating point (aka double in C++) und alle Operationen mit einfacher Genauigkeit (aka float in C++) emuliert werden in der Software unterstützen. Einige ältere 32-Bit-x86-CPUs arbeiteten auch intern mit 32-Bit-Ganzzahlen, und Operationen mit 16-Bit-Ganzzahlen erforderten mehr Taktzyklen aufgrund der Übersetzung von/zu 32-Bit (dies trifft nicht für modernere 32-Bit- oder 64-Bit-Prozessoren zu). Bit-x86-Prozessoren, da sie das Kopieren von 16-Bit-Ganzzahlen zu/von 32-Bit-Registern ermöglichen und auf diesen mit weniger Strafen arbeiten.

Es ist ein Kinderspiel, dass das Kopieren einer sehr großen Struktur nach Wert weniger effizient ist als das Erstellen und Kopieren seiner Adresse. Aber aufgrund von Faktoren wie dem oben Gesagten ist der Überkreuzungspunkt zwischen "am besten etwas von dieser Größe nach Wert kopieren" und "am besten, um seine Adresse zu übergeben" weniger klar.

Zeiger und Referenzen neigen dazu, auf ähnliche Weise implementiert zu werden (z. B. kann ein Durchgang durch Referenz auf die gleiche Weise implementiert werden wie ein Zeiger), aber das ist nicht garantiert.

Der einzige Weg, um sicher zu sein, ist es zu messen. Und erkennen Sie, dass die Messungen zwischen den Systemen variieren.

+2

Kennen Sie ein aktuelles Beispiel für eine Architektur, bei der das Übergeben eines kleineren Typs (z. B. ein Zeichen) teurer ist als das Übergeben eines größeren Typs (wie ein Int oder Zeiger)? – MikeMB

+0

Ja, okay, ein paar Beispiele hinzugefügt. – Peter

+0

Danke, aber ist eines dieser Beispiele relevant für die Frage nach Zeiger/Zeiger vs Wert? Schließlich geht es nicht darum, einen Float zu passieren, sondern ein Double zu passieren. – MikeMB

3

Bedeutet das, dass, wenn foo vom Typ float ist, es effizienter ist, foo einfach als Wert zu übergeben?

Das Übergeben eines Floatwerts könnte effizienter sein. Ich würde erwarten, dass es effizienter ist - teilweise aufgrund dessen, was Sie gesagt haben: Ein Float ist kleiner als ein Zeiger auf ein System, das Sie beschreiben. Wenn Sie jedoch den Zeiger kopieren, müssen Sie den Zeiger immer noch dereferenzieren, um den Wert innerhalb der Funktion abzurufen. Die durch den Zeiger hinzugefügte Indirektion könnte sich erheblich auf die Leistung auswirken.

Der Wirkungsgradunterschied könnte vernachlässigbar sein. Insbesondere wenn die Funktion inline ausgeführt werden kann und die Optimierung aktiviert ist, wird es wahrscheinlich keinen Unterschied geben.

Sie können herausfinden, ob es einen Leistungsgewinn gibt, wenn Sie den Float in Ihrem Fall durch Messung übergeben. Sie können die Effizienz mit einem Profilierungswerkzeug messen.

Sie können den Zeiger durch einen Verweis ersetzen, und die Antwort gilt immer noch genauso gut.

Gibt es irgendeine Art von Overhead bei der Verwendung einer Referenz, die Art, wie es ist, wenn ein Zeiger dereferenziert werden muss?

Ja. Es ist wahrscheinlich, dass eine Referenz genau die gleichen Leistungsmerkmale wie ein Zeiger hat. Wenn es möglich ist, ein semantisch äquivalentes Programm unter Verwendung von Verweisen oder Zeigern zu schreiben, werden beide wahrscheinlich identische Assembly generieren.


Wenn ein kleines Objekt durch Zeiger vorbei wäre schneller, als es zu kopieren, dann sicher wäre es für ein Objekt von gleicher Größe wahr sein, würden Sie nicht zustimmen? Wie wäre es mit einem Zeiger auf einen Zeiger, der ungefähr so ​​groß ist wie ein Zeiger? (Es ist genau die gleiche Größe.) Oh, aber Zeiger sind auch Objekte. Wenn das Übergeben eines Objekts (z. B. eines Zeigers) durch den Zeiger schneller ist als das Kopieren des Objekts (des Zeigers), wäre die Übergabe eines Zeigers an einen Zeiger auf einen Zeiger ... an einen Zeiger schneller als das Programm mit weniger Zeigern, die immer noch schneller sind als die, die keine Zeiger verwendet haben ... Perhaps haben wir hier eine unendliche Quelle der Effizienz gefunden :)

+0

Gibt es irgendeine Art von Overhead bei der Verwendung einer Referenz, die Art, wie es ist, wenn ein Zeiger dereferenziert werden muss? –

+0

@space_voyager Wie Zeiger, ja. Ich habe eine Bearbeitung hinzugefügt. – user2079303

2

Sie müssen ein beliebiges Szenario testen, in dem die Leistung absolut kritisch ist. Seien Sie jedoch sehr vorsichtig, wenn Sie versuchen, den Compiler dazu zu zwingen, Code auf eine bestimmte Weise zu generieren.

Der Optimierer des Compilers kann Ihren Code beliebig umschreiben, solange das Endergebnis das gleiche ist, was zu einigen sehr schönen Optimierungen führen kann.

Beachten Sie, dass das Übergeben eines Gleitkommawerts nach Wert eine Kopie des Gleitkommaformats erfordert, aber unter den richtigen Bedingungen das Übergeben eines Gleitkommawerts durch Referenz das Speichern des ursprünglichen Gleitkommas in einem CPU-Gleitkommaregister zulassen und dieses Register als "Reference" -Parameter für die Funktion. Im Gegensatz dazu, wenn Sie eine Kopie übergeben, muss der Compiler einen Platz finden, um die Kopie zu speichern, um den Inhalt des Registers zu bewahren, oder noch schlimmer, es ist möglicherweise nicht in der Lage, ein Register überhaupt zu verwenden, weil das Original erhalten (dies gilt insbesondere für rekursive Funktionen!). Dieser Unterschied ist auch wichtig, wenn Sie den Verweis auf eine Funktion übergeben, die inline sein könnte, wobei der Verweis die Kosten für das Inlining reduzieren könnte, da der Compiler nicht garantieren muss, dass ein kopierter Parameter das Original nicht ändern kann.

Je mehr eine Sprache es Ihnen erlaubt, sich auf die Beschreibung zu konzentrieren, was Sie tun wollen und nicht wie Sie es wollen, desto mehr kann der Compiler kreative Wege finden, die harte Arbeit für Sie zu erledigen.In C++ ist es im Allgemeinen am besten, sich nicht um die Leistung zu sorgen, sondern sich darauf zu konzentrieren, das, was Sie wollen, so klar und einfach wie möglich zu beschreiben. Indem Sie versuchen, zu beschreiben, wie Sie die Arbeit erledigen wollen, werden Sie genauso oft verhindern, dass der Compiler seinen Job zur Optimierung Ihres Codes für Sie erledigt.

+1

Normalerweise ist es umgekehrt: Wenn Sie einen Parameter durch Referenz/Zeiger übergeben, dann muss dieser Parameter in der Praxis immer in den Speicher geschrieben werden, während die Übergabe nach Wert es manchmal erlaubt, die Daten in Registern zu halten. – MikeMB

+0

@MikeMB - das ist nicht der Fall in dem oben dargestellten Szenario, wo die Originalkopie in einem Register gespeichert ist; Die Übergabe von Wert erfordert eine andere Kopie, um den Inhalt des Originals zu erhalten, so dass entweder ein zusätzliches Register verwendet werden muss, falls eines verfügbar ist, oder die gesamte Registeroptimierung aufgrund zu geringer Register in den Speicher entladen werden muss. Im Gegensatz dazu könnte die Übergabe durch Referenz dem Compiler ermöglichen, das gleiche Register über beide Codeabschnitte hinweg zu teilen (insbesondere wenn die Funktion inline ist). Ich behaupte nicht, dass dies ein allgemeines Szenario ist, aber sicherlich möglich. –

+0

Angenommen, es findet keine Inlining-Funktion statt. Dann passiere ich durch Verweismittel - auf die aufrufenden Konventionen, die mir bekannt sind -, dass ein Zeiger auf den ursprünglichen Speicherplatz ** HAS ** an die Funktion übergeben werden muss und dass der Wert tatsächlich als ein Zeiger im Speicher gespeichert werden muss kann nicht auf ein Register zeigen. Bei der Wertübergabe müssen Sie möglicherweise den Falue von einem Register in ein anderes kopieren (nicht, wenn der Wert nach dem Funktionsaufruf nicht verwendet wird), aber Sie müssen ihn nicht im Speicher ablegen. – MikeMB

Verwandte Themen