Bevor Sie beim doppelten Titel zucken, war die andere Frage nicht passend zu dem, was ich hier frage (IMO). Also.
Ich möchte wirklich virtuelle Funktionen in meiner Anwendung verwenden, um die Dinge hundertmal leichter zu machen (darum geht es bei OOP nicht;)). Aber ich las irgendwo, wo sie zu einem Performance-Preis kamen, und sah nichts anderes als den gleichen, altbekannten Hype einer vorzeitigen Optimierung. Ich entschied mich, es in einem kleinen Benchmark-Test mit einem Schnellstrudel zu versuchen:
CProfiler.cpp
%Vor%main.cpp
%Vor%Zuerst habe ich nur 100 und 10000 Iterationen gemacht, und die Ergebnisse waren besorgniserregend: 4 ms für nicht virtualisierte und 250 ms für virtualisierte! Ich ging fast "nooooooo" drinnen, aber dann habe ich die Iterationen auf etwa 500.000 erhöht; um zu sehen, dass die Ergebnisse fast vollständig identisch sind (vielleicht 5% langsamer ohne aktivierte Optimierungsflags).
Meine Frage ist, warum gab es so eine signifikante Änderung mit einer geringen Anzahl von Iterationen im Vergleich zu hohen Mengen? War es nur, weil die virtuellen Funktionen bei diesen vielen Iterationen im Cachespeicher sind?
Haftungsausschluss
Ich verstehe, dass mein "Profiling" -Code nicht perfekt ist, aber es gibt, wie es ist, eine Einschätzung der Dinge, was alles auf dieser Ebene zählt. Auch ich stelle diese Fragen, um nicht nur meine Bewerbung zu optimieren.
Erweitern Charles 'Antwort .
Das Problem hierbei ist, dass Ihre Schleife mehr tut als nur den virtuellen Anruf selbst zu testen (die Speicherzuweisung stellt wahrscheinlich ohnehin den virtuellen Anruf-Overhead in den Schatten), also schlägt er vor, den Code so zu ändern, dass nur der virtuelle Anruf getestet wird.
Hier ist die Benchmark-Funktion eine Vorlage, weil die Vorlage inline sein kann, während es unwahrscheinlich ist, dass Funktionszeiger sie aufrufen.
%Vor%Klassen:
%Vor%Und das Messen:
%Vor%Hinweis: Dies testet den Overhead eines virtuellen Aufrufs im Vergleich zu einer regulären Funktion. Die Funktionalität ist anders (da Sie im zweiten Fall keine Laufzeitverteilung haben), aber es ist daher ein Overhead im schlimmsten Fall.
BEARBEITEN :
Ergebnisse des Laufs (gcc.3.4.2, -O2, SLES10 Quadcore-Server) Hinweis: Mit den Funktionen Definitionen in einer anderen Übersetzungseinheit, um Inlining zu verhindern
%Vor%Nicht wirklich überzeugend.
Ich glaube, dass Ihr Testfall zu künstlich ist, um von großem Wert zu sein.
Erstens, innerhalb Ihrer profilierten Funktion werden Sie dynamisch ein Objekt zuweisen und freigeben sowie eine Funktion aufrufen, wenn Sie nur den Funktionsaufruf profilieren möchten, dann sollten Sie genau das tun.
Zweitens erstellen Sie keinen Fall, in dem ein virtueller Funktionsaufruf eine praktikable Alternative zu einem bestimmten Problem darstellt. Ein virtueller Funktionsaufruf bietet dynamischen Versand. Sie sollten versuchen, einen Fall zu profilieren, z. B. wenn ein virtueller Funktionsaufruf als Alternative zu etwas verwendet wird, das ein Anti-Pattern vom Typ "Einschalttyp" verwendet.
Bei einer kleinen Anzahl von Iterationen besteht die Möglichkeit, dass Ihr Code mit einem anderen Programm, das parallel ausgeführt wird oder ein Swapping stattfindet, ausgeschlossen wird. Ansonsten wird das Betriebssystem vom Betriebssystem getrennt System in Ihre Benchmark-Ergebnisse aufgenommen. Dies ist der Hauptgrund, warum Sie Ihren Code etwa ein Dutzend Millionen Mal ausführen sollten, um etwas mehr oder weniger zuverlässig zu messen.
Ich denke, dass diese Art von Tests tatsächlich ziemlich nutzlos ist:
1) Sie verschwenden Zeit für das Profiling selbst Aufruf gettimeofday()
;
2) Sie testen nicht wirklich virtuelle Funktionen, und IMHO ist dies das Schlimmste.
Warum? Weil Sie virtuelle Funktionen verwenden, um das Schreiben von Dingen wie:
zu vermeiden %Vor%In diesem Code vermissen Sie den Block "if ... else", so dass Sie den Vorteil virtueller Funktionen nicht wirklich nutzen können. Dies ist ein Szenario, in dem sie immer "Verlierer" gegen nicht-virtuelle sind.
Um eine korrekte Profilerstellung vorzunehmen, sollten Sie etwas hinzufügen, wie den Code, den ich gepostet habe.
Das Aufrufen einer virtuellen Funktion hat Auswirkungen auf die Leistung, da sie etwas mehr als das Aufrufen einer regulären Funktion bewirkt. In einer realen Anwendung dürften die Auswirkungen jedoch vernachlässigbar sein - noch weniger sogar in den am besten ausgearbeiteten Benchmarks.
In einer realen Anwendung wird die Alternative zu einer virtuellen Funktion normalerweise dazu führen, dass Sie irgendein ähnliches System handschriftlich schreiben, weil sich das Verhalten beim Aufruf einer virtuellen Funktion und beim Aufruf einer nicht-virtuellen Funktion unterscheidet - die ersteren ändern sich basierend auf dem Laufzeittyp des aufrufenden Objekts. Ihr Maßstab, selbst wenn er irgendwelche Fehler ignoriert, misst nicht das äquivalente Verhalten, sondern nur die äquivalente Syntax. Wenn Sie eine Codierungsrichtlinie einführen würden, die virtuelle Funktionen verbietet, müssten Sie entweder einen potenziell sehr umständlichen oder verwirrenden Code schreiben (der langsamer sein könnte) oder eine ähnliche Art von Runtime-Dispatch-System implementieren, die der Compiler zur Implementierung von virtuell verwendet Funktionsverhalten (was in den meisten Fällen sicherlich nicht schneller ist als das, was der Compiler tut).
Es könnte mehrere Gründe für den Zeitunterschied geben.
Der Heap-Manager kann das Ergebnis beeinflussen, weil sizeof(VCS) > sizeof(VS)
. Was passiert, wenn Sie new
/ delete
aus der Schleife entfernen?
Aufgrund von Größenunterschieden kann der Speichercache tatsächlich Teil des Zeitunterschieds sein.
ABER: Sie sollten wirklich ähnliche Funktionen vergleichen. Wenn Sie virtuelle Funktionen verwenden, tun Sie dies aus einem Grund, der abhängig von der Identität des Objekts eine andere Elementfunktion aufruft. Wenn Sie diese Funktionalität benötigen und keine virtuellen Funktionen verwenden möchten, müssen Sie sie manuell implementieren, sei es mit einer Funktionstabelle oder mit einer switch-Anweisung. Das kostet auch etwas, und das sollten Sie mit virtuellen Funktionen vergleichen.
Bei zu wenigen Iterationen ist die Messung sehr gestört. Die Funktion gettimeofday
wird nicht genau genug sein, um Ihnen nur für eine Handvoll Iterationen gute Messwerte zu geben, ganz zu schweigen davon, dass sie die gesamte Wandzeit aufzeichnet (einschließlich Zeit, die von anderen Threads belegt wurde).
Unter dem Strich sollten Sie jedoch kein lächerlich gewundenes Design entwickeln, um virtuelle Funktionen zu vermeiden. Sie fügen wirklich nicht viel Overhead hinzu. Wenn Sie unglaublich leistungskritischen Code haben und wissen, dass virtuelle Funktionen die meiste Zeit ausmachen, dann müssen Sie sich vielleicht etwas Sorgen machen. In jeder praktischen Anwendung sind virtuelle Funktionen jedoch nicht das, was Ihre Anwendung verlangsamt.
Meiner Meinung nach, wenn es weniger Loops gab, gab es vielleicht keinen Kontextwechsel. Aber wenn Sie die Anzahl der Loops erhöht haben, gibt es sehr gute Chancen, dass Kontextwechsel stattfinden und das Lesen dominiert. Zum Beispiel dauert das erste Programm 1 Sekunde und das zweite Programm 3 Sekunden, aber wenn der Kontextwechsel 10 Sekunden dauert, ist die Differenz 13/11 anstelle von 3/1.
Tags und Links c++ inheritance performance virtual