Was ist die Kosten für die Verwendung eines Zeiger auf member-Funktion im Vergleich zu einem Schalter?
Ich habe die folgende situation:
class A
{
public:
A(int whichFoo);
int foo1();
int foo2();
int foo3();
int callFoo(); //cals one of the foo's depending on the value of whichFoo
};
In meiner aktuellen Implementierung, die ich speichern Sie den Wert von whichFoo
in ein Daten-member im Konstruktor, und verwenden Sie ein switch
im callFoo()
zu entscheiden, welche von den foo ' s zu nennen. Alternativ kann ich mit einem switch
im Konstruktor speichern einen Zeiger auf die richtige fooN()
genannt zu werden, in callFoo()
.
Meine Frage ist, welcher Weg ist effektiver, wenn ein Objekt der Klasse A wird nur einmal gebaut, während callFoo()
heißt eine sehr große Anzahl von Zeiten. Also im ersten Fall haben wir mehrere Ausführungen einer switch-Anweisung, während in der zweiten gibt es nur einen Schalter, und mehrere Anrufe von eine member-Funktion mit dem Zeiger darauf. Ich weiß, dass der Aufruf einer member-Funktion mit einem Zeiger langsamer ist, als nur ruft es direkt. Weiß jemand, ob dieser Aufwand ist mehr oder weniger als die Kosten für eine switch
?
Klarstellung: ich weiß, dass Sie nie wirklich wissen, welche Ansatz bietet eine bessere Leistung, bis Sie es versuchen und Zeit. In diesem Fall ist aber ich habe schon Ansatz 1 umgesetzt, und ich wollte herausfinden, ob Ansatz 2 kann es effizienter sein, zumindest im Prinzip. Es scheint, dass es sein kann, und jetzt macht es Sinn für mich, zu stören, um es zu implementieren, und versuchen Sie es.
Oh, und ich mag auch Ansatz 2 besser aus ästhetischen Gründen. Ich denke, ich bin auf der Suche nach einer Rechtfertigung, um es zu implementieren. 🙂
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wie sicher sind Sie, dass der Aufruf einer member-Funktion über einen Zeiger langsamer ist, als nur nannte es nicht direkt? Kann man den Unterschied Messen?
In der Regel sollten Sie nicht auf Ihre intuition verlassen, wenn die Leistung Bewertungen. Setzen Sie sich mit Ihrem compiler und eine timing-Funktion, und tatsächlich Messen die verschiedenen Möglichkeiten. Sie werden vielleicht überrascht sein!
Weitere Infos: Es gibt einen exzellenten Artikel Member-Funktion-Zeiger und die Schnellste Mögliche C++ - Delegierten die geht in sehr Tiefe detailliert über die Umsetzung des member-Funktion-Zeiger.
Können Sie dies schreiben:
Die Berechnung der eigentlichen Funktion Zeiger ist linear und schnell:
Beachten Sie, dass Sie nicht selbst lösen können, die eigentliche Funktion Anzahl im Konstruktor.
Habe ich gegenüber diesen code, um das asm generiert eine
switch
. Dieswitch
version bietet keine Leistungssteigerung.Antwort auf die gestellte Frage: am besten feinkörnigen Ebene, die Zeiger auf den member-Funktion eine bessere Leistung.
Adresse die unausgesprochene Frage: was bedeutet "besser" hier bedeuten? In den meisten Fällen würde ich erwarten, dass der Unterschied zu vernachlässigen sein. Je nachdem, was die Klasse tut es, allerdings ist der Unterschied erheblich sein kann. Performance-Tests bevor Sie sich Gedanken über den Unterschied ist offensichtlich der richtige erste Schritt.
Wenn Sie gehen, um mit einem Schalter, der völlig in Ordnung ist, dann sollte man wohl die Logik in eine helper-Methode, und rufen Sie, wenn Sie aus dem Konstruktor. Alternativ, dies ist ein klassischer Fall der Strategie-Muster. Sie könnte eine Schnittstelle erstellen (oder abstrakte Klasse) mit dem Namen "ifoo") vom die hat eine Methode, mit Foo ' s Signatur. Hätte man den Konstruktor einer Instanz von "ifoo") vom (Konstruktor Abhängigkeit Injektion umgesetzt, dass die foo Methode, die Sie möchten. Sie hätten ein eigenes "ifoo") vom, die festlegen würde, mit diesem Konstruktor, und jedes mal, wenn Sie anrufen wollte Foo-rufen Sie Ihre "ifoo") vom s-version.
Hinweis: ich habe noch nicht mit C++ arbeitete seit dem college, also mein lingo-Code werden Sie vielleicht hier, ut die Allgemeinen Ideen halten, für die meisten OO-Sprachen.
Wenn Ihr Beispiel ist real code, dann denke ich, Sie sollten überprüfen Sie Ihre Klasse-design. Übergeben den Wert an den Konstruktor, verwendet wird, um Verhalten zu ändern, ist wirklich das äquivalent zum erstellen einer Unterklasse. Betrachten refactoring, um es deutlicher. Der Effekt dabei ist, dass Ihr code wird am Ende mit einem Funktionszeiger (alle virtuelle Methoden sind, wirklich, sind Funktionszeiger von jump tables).
Wenn aber dein code war nur ein Vereinfachtes Beispiel, um zu Fragen, ob, allgemein -, Sprung-Tabellen sind schneller als switch-Anweisungen, dann ist meine intuition würde sagen, dass die Sprung-Tabellen sind schneller, aber Sie sind abhängig von der compiler-Optimierung Schritt. Aber wenn die Leistung wirklich so ein Anliegen ist, nie verlassen sich auf intuition - klopfen ein test-Programm und testen Sie es, oder schauen Sie sich den erzeugten assembler.
Eins ist sicher, eine switch-Anweisung wird nie langsamer sein als eine jump-Tabelle. Der Grund dafür ist, dass die besten einen compiler Optimizer tun können, wird auch wiederum eine Reihe von bedingten tests (z.B. einen Schalter) in eine jump-Tabelle. Also, wenn Sie wirklich sicher sein wollen, nehmen Sie den compiler aus der Entscheidung, verarbeiten und nutzen eine jump-Tabelle.
Klingt wie Sie sollte machen
callFoo
eine rein virtuelle Funktion, und erstellen Sie einige Unterklassen vonA
.Es sei denn, Sie wirklich brauchen, die Geschwindigkeit, getan haben umfangreiche profiling-und Instrumentierung, und festgestellt, dass die Anrufe zu
callFoo
sind wirklich der Flaschenhals. Haben Sie?Funktion Zeiger sind fast immer besser als angekettet-ifs. Machen Sie sauberer code, und sind fast immer schneller (außer vielleicht in einem Fall, wo nur die Wahl zwischen zwei Funktionen und ist immer richtig vorhergesagt).
Dass ich glaube, dass die Zeiger schneller gehen würde.
Modernen CPUs prefetch-Anweisungen; falsch vorhergesagte Verzweigungen Spülen Sie den cache, was bedeutet, es Stände, während es füllt den cache. Ein Zeiger doens nicht.
Sollte man natürlich Messen.
Optimieren nur bei Bedarf
Ersten: die Meiste Zeit werden Sie wahrscheinlich zu tun ist egal, der Unterschied wird sehr klein sein. Stellen Sie sicher, dass die Optimierung dieser Aufruf macht wirklich Sinn ersten. Nur wenn Sie Ihre Messungen zeigen, gibt es wirklich bedeutende Zeit in der call-overhead, gehen Sie zu optimieren (shameless plug - Vgl. So optimieren Sie eine Anwendung, um es schneller zu machen? ), Wenn die Optimierung nicht signifikant sind, bevorzugen die besser lesbaren code.
Indirekten Aufruf Kosten hängen von der Zielplattform
Sobald Sie festgestellt haben, lohnt es sich zu bewerben, low-level-Optimierung, dann ist es eine Zeit, zu verstehen, Ihre Ziel-Plattform. Die Kosten können Sie vermeiden, ist hier die branch misprediction penalty. Auf modernen x86/x64 CPU dieser misprediction ist wahrscheinlich sehr klein ist (Sie kann Vorhersagen, indirekte Anrufe ganz gut die meisten der Zeit), aber wenn Sie auf PowerPC-oder anderen RISC-Plattformen, die indirekten Aufrufe/Sprünge sind oft nicht vorhergesagt und die Vermeidung von Ihnen können zu erheblichen performance-Gewinn. Siehe auch Virtuelle call Kosten hängen von der Plattform.
- Compiler implementieren können, wechseln Sie mit der jump-Tabelle als auch
Einer gotcha: Schalter manchmal kann implementiert werden als ein Indirekter Aufruf (mit einer Tabelle) als auch, vor allem beim Wechsel zwischen den vielen möglichen Werten. Ein solcher Schalter weist die gleichen misprediction wie eine virtuelle Funktion. Um diese Optimierung zuverlässig, vermutlich würde man es vorziehen, wenn anstelle der Schalter für den meisten Fällen.
Verwenden Sie Timer, um zu sehen, welche ist schneller. Obwohl, es sei denn, dieser code wird über und über, dann ist es unwahrscheinlich, dass Sie keinen Unterschied bemerken.
Sicher sein, dass, wenn Sie mit code aus dem Konstruktor aufgerufen werden, wenn die Konstruktion versagt, dass Sie nicht Speicherleck.
Diese Technik verwendet wird, stark mit Symbian OS:
http://www.titu.jyu.fi/modpa/Patterns/pattern-TwoPhaseConstruction.html
Wenn Sie nur aufrufen callFoo() einmal, als wahrscheinlich der Funktionszeiger wird langsamer sein, indem Sie einen unbedeutenden Betrag. Wenn Sie nennen es oft als wahrscheinlich die Funktion Zeiger wird schneller von einem geringfügigen Betrag (weil Sie nicht nötig zu halten, die durch den Schalter).
Entweder Weg, schauen auf die versammelten code, um herauszufinden, für sicher, dass Sie tut, was Sie denken, es tut.
Ein oft übersehener Vorteil, zu wechseln (auch über die Sortierung und Indizierung), wenn Sie wissen, dass ein bestimmter Wert verwendet wird, in der überwiegenden Mehrzahl der Fälle.
Es ist einfach, um den Schalter so, dass die häufigsten zuerst geprüft werden.
ps. Zur Verstärkung greg ' s Antwort, wenn Sie sich sorgen über Geschwindigkeit - Messen.
Suche bei assembler nicht helfen, wenn die CPUs haben prefetch /predictive Verzweigung und pipeline-stalls etc