In meinem Klassendesign verwende ich häufig abstrakte Klassen und virtuelle Funktionen. Ich hatte das Gefühl, dass virtuelle Funktionen die Leistung beeinflussen. Ist das wahr? Aber ich denke, dieser Leistungsunterschied ist nicht spürbar und sieht so aus, als würde ich vorzeitig optimieren. Richtig?
c++
performance
optimization
virtual-functions
Navaneeth KN
quelle
quelle
Antworten:
Eine gute Faustregel lautet:
Die Verwendung virtueller Funktionen wirkt sich nur geringfügig auf die Leistung aus, es ist jedoch unwahrscheinlich, dass sich dies auf die Gesamtleistung Ihrer Anwendung auswirkt. Bessere Orte, um nach Leistungsverbesserungen zu suchen, sind Algorithmen und E / A.
Ein ausgezeichneter Artikel, der sich mit virtuellen Funktionen (und mehr) befasst, sind Member Function Pointers und die schnellstmöglichen C ++ - Delegaten .
quelle
Ihre Frage hat mich neugierig gemacht, also habe ich einige Timings auf der 3-GHz-PowerPC-CPU ausgeführt, mit der wir arbeiten. Der Test, den ich durchführte, bestand darin, eine einfache 4d-Vektorklasse mit get / set-Funktionen zu erstellen
Dann habe ich drei Arrays eingerichtet, die jeweils 1024 dieser Vektoren enthalten (klein genug, um in L1 zu passen), und eine Schleife ausgeführt, die sie 1000 Mal zueinander addiert (Ax = Bx + Cx). Ich habe dies mit den als
inline
, definierten Funktionenvirtual
und regulären Funktionsaufrufen ausgeführt. Hier sind die Ergebnisse:In diesem Fall (wo alles in den Cache passt) waren die virtuellen Funktionsaufrufe etwa 20-mal langsamer als die Inline-Aufrufe. Aber was bedeutet das wirklich? Jede Fahrt durch die Schleife verursachte genau
3 * 4 * 1024 = 12,288
Funktionsaufrufe (1024 Vektoren mal vier Komponenten mal drei Aufrufe pro Addition), so dass diese Zeiten1000 * 12,288 = 12,288,000
Funktionsaufrufe darstellen. Die virtuelle Schleife dauerte 92 ms länger als die direkte Schleife, sodass der zusätzliche Overhead pro Aufruf 7 Nanosekunden pro Funktion betrug .Daraus schließe ich: Ja , virtuelle Funktionen sind viel langsamer als direkte Funktionen, und nein , es sei denn, Sie planen, sie zehn Millionen Mal pro Sekunde aufzurufen, spielt es keine Rolle.
Siehe auch: Vergleich der generierten Baugruppe.
quelle
Wenn Objective-C (bei dem alle Methoden virtuell sind) die Hauptsprache für das iPhone ist und Java die Hauptsprache für Android ist, ist es meiner Meinung nach ziemlich sicher, virtuelle C ++ - Funktionen auf unseren 3-GHz-Dual-Core-Türmen zu verwenden.
quelle
In sehr leistungskritischen Anwendungen (wie Videospielen) kann ein virtueller Funktionsaufruf zu langsam sein. Bei moderner Hardware ist das größte Leistungsproblem der Cache-Fehler. Wenn sich Daten nicht im Cache befinden, kann es Hunderte von Zyklen dauern, bis sie verfügbar sind.
Ein normaler Funktionsaufruf kann einen Befehls-Cache-Fehler erzeugen, wenn die CPU den ersten Befehl der neuen Funktion abruft und er sich nicht im Cache befindet.
Ein virtueller Funktionsaufruf muss zuerst den vtable-Zeiger aus dem Objekt laden. Dies kann zu einem Datencache-Fehler führen. Dann lädt es den Funktionszeiger aus der vtable, was zu einem weiteren Datencachefehler führen kann. Dann ruft es die Funktion auf, die wie eine nicht virtuelle Funktion zu einem Befehls-Cache-Fehler führen kann.
In vielen Fällen sind zwei zusätzliche Cache-Fehler kein Problem, aber in einer engen Schleife für leistungskritischen Code kann dies die Leistung drastisch verringern.
quelle
Ab Seite 44 des Agner Fog-Handbuchs "Software in C ++ optimieren" :
quelle
switch
.case
Sicher mit völlig willkürlichen Werten. Aber wenn allecase
s aufeinander folgen, kann ein Compiler dies möglicherweise in eine Sprungtabelle optimieren (ah, das erinnert mich an die guten alten Z80-Tage), die (aus Mangel an einem besseren Begriff) konstant sein sollte. Nicht, dass ich empfehlen würde, vfuncs durch zu ersetzenswitch
, was lächerlich ist. ;)absolut. Es war vor langer Zeit ein Problem, als Computer mit 100 MHz betrieben wurden, da für jeden Methodenaufruf eine Suche in der vtable erforderlich war, bevor sie aufgerufen wurde. Aber heute ... auf einer 3-GHz-CPU mit Cache der ersten Ebene und mehr Speicher als mein erster Computer? Überhaupt nicht. Das Zuweisen von Speicher aus dem Hauptspeicher kostet Sie mehr Zeit als wenn alle Ihre Funktionen virtuell wären.
Es ist wie in alten Zeiten, in denen die Leute sagten, strukturierte Programmierung sei langsam, weil der gesamte Code in Funktionen aufgeteilt war, jede Funktion Stapelzuordnungen und einen Funktionsaufruf erforderte!
Das einzige Mal, dass ich überhaupt daran denken würde, die Auswirkungen einer virtuellen Funktion auf die Leistung zu berücksichtigen, ist, wenn sie sehr häufig verwendet und in Vorlagencode instanziiert wurde, der in allem landete. Selbst dann würde ich nicht zu viel Mühe darauf verwenden!
PS denken an andere 'einfach zu bedienende' Sprachen - alle ihre Methoden sind virtuell und sie kriechen heutzutage nicht mehr.
quelle
Neben der Ausführungszeit gibt es noch weitere Leistungskriterien. Eine Vtable belegt ebenfalls Speicherplatz und kann in einigen Fällen vermieden werden: ATL verwendet zur Zeit der " simulierten dynamischen Bindung " zur Kompilierung mit Vorlagenden Effekt des "statischen Polymorphismus" zu erzielen, der schwer zu erklären ist; Grundsätzlich übergeben Sie die abgeleitete Klasse als Parameter an eine Basisklassenvorlage, sodass die Basisklasse zur Kompilierungszeit "weiß", was ihre abgeleitete Klasse in jedem Fall ist. Sie können nicht mehrere verschiedene abgeleitete Klassen in einer Sammlung von Basistypen speichern (das ist Laufzeitpolymorphismus), sondern aus statischer Sicht, wenn Sie eine Klasse Y erstellen möchten, die mit einer bereits vorhandenen Vorlagenklasse X identisch ist, die die hat Hooks für diese Art des Überschreibens müssen Sie nur die Methoden überschreiben, die Ihnen wichtig sind, und dann erhalten Sie die Basismethoden der Klasse X, ohne eine vtable zu haben.
In Klassen mit großem Speicherbedarf sind die Kosten für einen einzelnen vtable-Zeiger nicht hoch, aber einige der ATL-Klassen in COM sind sehr klein, und es lohnt sich, die vtable einzusparen, wenn der Fall des Laufzeitpolymorphismus niemals auftreten wird.
Siehe auch diese andere SO-Frage .
Übrigens, hier ist ein Beitrag , der über die Leistungsaspekte der CPU-Zeit spricht.
quelle
Ja, Sie haben Recht, und wenn Sie neugierig auf die Kosten eines virtuellen Funktionsaufrufs sind, finden Sie diesen Beitrag möglicherweise interessant.
quelle
Der einzige Weg, auf dem ich sehen kann, dass eine virtuelle Funktion zu einem Leistungsproblem wird, besteht darin, dass viele virtuelle Funktionen innerhalb einer engen Schleife aufgerufen werden und genau dann, wenn sie einen Seitenfehler oder eine andere "schwere" Speicheroperation verursachen.
Obwohl, wie andere Leute gesagt haben, es im wirklichen Leben so gut wie nie ein Problem für Sie sein wird. Wenn Sie der Meinung sind, führen Sie einen Profiler aus, führen Sie einige Tests durch und überprüfen Sie, ob dies wirklich ein Problem ist, bevor Sie versuchen, Ihren Code für einen Leistungsvorteil zu "entwerfen".
quelle
Wenn die Klassenmethode nicht virtuell ist, führt der Compiler normalerweise In-Lining durch. Wenn Sie dagegen einen Zeiger auf eine Klasse mit virtueller Funktion verwenden, ist die reale Adresse nur zur Laufzeit bekannt.
Dies wird durch Test, Zeitunterschied ~ 700% (!) Gut veranschaulicht:
Die Auswirkungen des Aufrufs virtueller Funktionen hängen stark von der Situation ab. Wenn es nur wenige Anrufe und einen erheblichen Arbeitsaufwand innerhalb der Funktion gibt, kann dies vernachlässigbar sein.
Oder wenn es sich um einen virtuellen Anruf handelt, der wiederholt verwendet wird, während eine einfache Operation ausgeführt wird, kann er sehr groß sein.
quelle
++ia
. Na und?Ich bin in meinem speziellen Projekt mindestens 20 Mal hin und her gegangen. Obwohl es kann einige große Vorteile in Bezug auf die Wiederverwendung von Code sein, Klarheit, Wartbarkeit und Lesbarkeit, auf der anderen Seite, Leistung Hits noch tun exist mit virtuellen Funktionen.
Wird sich der Leistungseinbruch auf einem modernen Laptop / Desktop / Tablet bemerkbar machen ... wahrscheinlich nicht! In bestimmten Fällen bei eingebetteten Systemen kann der Leistungseinbruch jedoch der treibende Faktor für die Ineffizienz Ihres Codes sein, insbesondere wenn die virtuelle Funktion in einer Schleife immer wieder aufgerufen wird.
Hier ist ein etwas datiertes Papier, das Best Practices für C / C ++ im Kontext eingebetteter Systeme analysiert: http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_Boston_01_304_paper.pdf
Fazit: Es ist Sache des Programmierers, die Vor- und Nachteile der Verwendung eines bestimmten Konstrukts gegenüber einem anderen zu verstehen. Wenn Sie nicht besonders leistungsorientiert sind, interessiert Sie der Leistungseinbruch wahrscheinlich nicht und Sie sollten alle netten OO-Inhalte in C ++ verwenden, um Ihren Code so benutzerfreundlich wie möglich zu gestalten.
quelle
Nach meiner Erfahrung ist das Wichtigste die Fähigkeit, eine Funktion zu integrieren. Wenn Sie Leistungs- / Optimierungsanforderungen haben, die vorschreiben, dass eine Funktion eingebunden werden muss, können Sie die Funktion nicht virtuell machen, da dies dies verhindern würde. Andernfalls werden Sie den Unterschied wahrscheinlich nicht bemerken.
quelle
Eine Sache zu beachten ist, dass dies:
kann schneller sein als dies:
Dies liegt daran, dass die erste Methode nur eine Funktion aufruft, während die zweite möglicherweise viele verschiedene Funktionen aufruft. Dies gilt für jede virtuelle Funktion in einer beliebigen Sprache.
Ich sage "darf", weil dies vom Compiler, dem Cache usw. abhängt.
quelle
Der Leistungsverlust bei der Verwendung virtueller Funktionen kann die Vorteile, die Sie auf Designebene erhalten, niemals übersteigen. Angeblich wäre ein Aufruf einer virtuellen Funktion 25% weniger effizient als ein direkter Aufruf einer statischen Funktion. Dies liegt daran, dass die VMT eine gewisse Indirektionsebene aufweist. Die für den Anruf benötigte Zeit ist jedoch normalerweise sehr gering im Vergleich zu der Zeit, die für die tatsächliche Ausführung Ihrer Funktion benötigt wird, sodass die Gesamtkosten für die Leistung, insbesondere bei der aktuellen Leistung der Hardware, vernachlässigbar sind. Darüber hinaus kann der Compiler manchmal optimieren und feststellen, dass kein virtueller Aufruf erforderlich ist, und ihn zu einem statischen Aufruf kompilieren. Machen Sie sich also keine Sorgen, verwenden Sie virtuelle Funktionen und abstrakte Klassen so oft, wie Sie benötigen.
quelle
The performance penalty of using virtual functions can sometimes be so insignificant that it is completely outweighed by the advantages you get at the design level.
Der Hauptunterschied istsometimes
, nicht zu sagennever
.Ich habe mich das immer selbst in Frage gestellt, zumal ich vor einigen Jahren auch einen solchen Test durchgeführt habe, bei dem ich die Zeiten eines Standard-Methodenaufrufs für Mitglieder mit einem virtuellen verglichen habe, und mich über die damaligen Ergebnisse sehr geärgert habe, da leere virtuelle Anrufe vorhanden waren 8 mal langsamer als Nicht-Virtuals.
Heute musste ich mich entscheiden, ob ich eine virtuelle Funktion zum Zuweisen von mehr Speicher in meiner Pufferklasse in einer sehr leistungskritischen App verwenden wollte oder nicht, also habe ich gegoogelt (und dich gefunden) und am Ende den Test erneut durchgeführt.
Und war wirklich überrascht, dass es - eigentlich - überhaupt nicht mehr wichtig ist. Es ist zwar nur sinnvoll, Inlines schneller als Nicht-Virtuals zu haben, und sie sind schneller als Virtuals, aber es kommt häufig auf die Gesamtlast des Computers an, ob Ihr Cache über die erforderlichen Daten verfügt oder nicht, und ob Sie möglicherweise optimieren können Auf Cache-Ebene denke ich, dass dies mehr von den Compiler-Entwicklern als von Anwendungsentwicklern getan werden sollte.
quelle