Virtuelle Funktionen und Leistung - C ++

125

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?

Navaneeth KN
quelle
Gemäß
Suma
2
Wenn Sie Hochleistungsrechnen und Zahlenverarbeitung durchführen, verwenden Sie keine Virtualität im Kern der Berechnung: Sie beeinträchtigt definitiv alle Leistungen und verhindert Optimierungen beim Kompilieren. Für die Initialisierung oder Finalisierung des Programms ist dies nicht wichtig. Wenn Sie mit Schnittstellen arbeiten, können Sie die Virtualität nach Ihren Wünschen verwenden.
Vincent

Antworten:

90

Eine gute Faustregel lautet:

Es ist kein Leistungsproblem, bis Sie es beweisen können.

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 .

Greg Hewgill
quelle
Was ist mit reinen virtuellen Funktionen? Beeinflussen sie die Leistung in irgendeiner Weise? Ich frage mich nur, wie es scheint, dass sie nur da sind, um die Implementierung durchzusetzen.
thomthom
2
@thomthom: Richtig, es gibt keinen Leistungsunterschied zwischen rein virtuellen und normalen virtuellen Funktionen.
Greg Hewgill
168

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

class TestVec 
{
    float x,y,z,w; 
public:
    float GetX() { return x; }
    float SetX(float to) { return x=to; }  // and so on for the other three 
}

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 Funktionen virtualund regulären Funktionsaufrufen ausgeführt. Hier sind die Ergebnisse:

  • Inline: 8 ms (0,65 ns pro Anruf)
  • direkt: 68 ms (5,53 ns pro Anruf)
  • virtuell: 160 ms (13 ns pro Anruf)

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,288Funktionsaufrufe (1024 Vektoren mal vier Komponenten mal drei Aufrufe pro Addition), so dass diese Zeiten 1000 * 12,288 = 12,288,000Funktionsaufrufe 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.

Crashworks
quelle
Wenn sie jedoch mehrmals angerufen werden, können sie oft günstiger sein als wenn sie nur einmal angerufen werden. Siehe meinen irrelevanten Blog: phresnel.org/blog , die Beiträge mit dem Titel "Virtuelle Funktionen gelten als nicht schädlich", aber natürlich hängt es von der Komplexität Ihrer Codepfade ab
Sebastian Mach
21
Mein Test misst einen kleinen Satz virtueller Funktionen, die wiederholt aufgerufen werden. In Ihrem Blog-Beitrag wird davon ausgegangen, dass die Zeitkosten von Code durch Zählen von Vorgängen gemessen werden können. Dies ist jedoch nicht immer der Fall. Die Hauptkosten eines Vfunc auf modernen Prozessoren sind die Pipeline-Blasen, die durch eine Fehlprognose der Zweigstelle verursacht werden.
Crashworks
10
Dies wäre ein großartiger Benchmark für gcc LTO (Link Time Optimization). Versuchen Sie dies erneut mit aktiviertem lto zu kompilieren: gcc.gnu.org/wiki/LinkTimeOptimization und sehen Sie, was mit dem 20x-Faktor passiert
lurscher
1
Wenn eine Klasse eine virtuelle und eine Inline-Funktion hat, wird dann auch die Leistung der nicht virtuellen Methode beeinträchtigt? Einfach aufgrund der Art der Klasse, die virtuell ist?
Thomthom
4
@thomthom Nein, virtuell / nicht virtuell ist ein Attribut pro Funktion. Eine Funktion muss nur über vtable definiert werden, wenn sie als virtuell markiert ist oder wenn sie eine Basisklasse überschreibt, die sie als virtuell hat. Sie werden häufig Klassen sehen, die eine Gruppe virtueller Funktionen für die öffentliche Schnittstelle haben, und dann viele Inline-Accessoren und so weiter. (Technisch gesehen ist dies implementierungsspezifisch und ein Compiler könnte virtuelle Ponter auch für Funktionen verwenden, die als "Inline" gekennzeichnet sind, aber eine Person, die einen solchen Compiler geschrieben hat, wäre verrückt.)
Crashworks
42

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.

Futter
quelle
4
Ich bin nicht sicher, ob das iPhone ein gutes Beispiel für performanten Code ist: youtube.com/watch?v=Pdk2cJpSXLg
Crashworks
13
@Crashworks: Das iPhone ist überhaupt kein Beispiel für Code. Es ist ein Beispiel für Hardware - speziell langsame Hardware , worauf ich hier hingewiesen habe. Wenn diese angeblich "langsamen" Sprachen gut genug für unterlastete Hardware sind, werden virtuelle Funktionen kein großes Problem sein.
Chuck
52
Das iPhone läuft auf einem ARM-Prozessor. Die für iOS verwendeten ARM-Prozessoren sind für niedrige MHz und geringen Stromverbrauch ausgelegt. Es gibt kein Silizium für die Verzweigungsvorhersage auf der CPU und daher keinen Leistungsaufwand durch Verzweigungsvorhersagefehler bei virtuellen Funktionsaufrufen. Außerdem ist der MHz für iOS-Hardware niedrig genug, dass ein Cache-Fehler den Prozessor nicht für 300 Taktzyklen blockiert, während er Daten aus dem RAM abruft. Cache-Fehler sind bei niedrigeren MHz weniger wichtig. Kurz gesagt, die Verwendung virtueller Funktionen auf iOS-Geräten verursacht keinen Overhead. Dies ist jedoch ein Hardwareproblem und gilt nicht für Desktops-CPUs.
HaltingState
3
Als langjähriger Java-Programmierer, der neu in C ++ eingeführt wurde, möchte ich hinzufügen, dass Javas JIT-Compiler und Laufzeitoptimierer in der Lage sind, einige Funktionen zur Laufzeit nach einer vordefinierten Anzahl von Schleifen zu kompilieren, vorherzusagen und sogar zu integrieren. Ich bin mir jedoch nicht sicher, ob C ++ beim Kompilieren und Verknüpfen über eine solche Funktion verfügt, da das Laufzeitaufrufmuster fehlt. Daher müssen wir in C ++ möglicherweise etwas vorsichtiger sein.
Alex Suo
@AlexSuo Ich bin mir nicht sicher, was Sie meinen? Beim Kompilieren kann C ++ natürlich nicht basierend auf dem, was zur Laufzeit passieren könnte, optimieren, sodass die Vorhersage usw. von der CPU selbst durchgeführt werden müsste ... aber gute C ++ - Compiler (wenn angewiesen) sind sehr bemüht, Funktionen und Schleifen lange vorher zu optimieren Laufzeit.
underscore_d
34

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.

Mark James
quelle
6
Richtig, aber jeder Code (oder jede vtable), der wiederholt aus einer engen Schleife aufgerufen wird, wird (natürlich) selten Cache-Fehler erleiden. Außerdem befindet sich der vtable-Zeiger normalerweise in derselben Cache-Zeile wie andere Daten im Objekt, auf die die aufgerufene Methode zugreifen wird. Daher handelt es sich häufig nur um einen zusätzlichen Cache-Fehler.
Qwertie
5
@ Qwertie Ich denke nicht, dass das notwendig ist, stimmt. Der Hauptteil der Schleife (wenn er größer als der L1-Cache ist) könnte den vtable-Zeiger, den Funktionszeiger und die nachfolgende Iteration "zurückziehen" und müsste bei jeder Iteration auf den Zugriff auf den L2-Cache (oder mehr) warten
Ghita
30

Ab Seite 44 des Agner Fog-Handbuchs "Software in C ++ optimieren" :

Die Zeit, die zum Aufrufen einer virtuellen Elementfunktion benötigt wird, beträgt einige Taktzyklen mehr als zum Aufrufen einer nicht virtuellen Elementfunktion, vorausgesetzt, die Funktionsaufrufanweisung ruft immer dieselbe Version der virtuellen Funktion auf. Wenn sich die Version ändert, erhalten Sie eine Strafe für Fehlvorhersagen von 10 bis 30 Taktzyklen. Die Regeln für die Vorhersage und Fehlvorhersage von virtuellen Funktionsaufrufen sind dieselben wie für switch-Anweisungen ...

Boojum
quelle
Vielen Dank für diesen Hinweis. Die Optimierungshandbücher von Agner Fog sind der Goldstandard für die optimale Nutzung der Hardware.
Arto Bendiken
Aufgrund meiner Erinnerung und einer schnellen Suche - stackoverflow.com/questions/17061967/c-switch-and-jump-tables - bezweifle ich, dass dies immer zutrifft switch. caseSicher mit völlig willkürlichen Werten. Aber wenn alle cases 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 ersetzen switch, was lächerlich ist. ;)
underscore_d
7

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.

gbjbaanb
quelle
4
Das Vermeiden von Funktionsaufrufen ist auch heute noch wichtig für leistungsstarke Apps. Der Unterschied besteht darin, dass die heutigen Compiler kleine Funktionen zuverlässig inline integrieren, sodass wir beim Schreiben kleiner Funktionen keine Geschwindigkeitseinbußen erleiden. Bei virtuellen Funktionen können intelligente CPUs eine intelligente Verzweigungsvorhersage für sie durchführen. Die Tatsache, dass alte Computer langsamer waren, ist meines Erachtens nicht wirklich das Problem - ja, sie waren viel langsamer, aber damals wussten wir das, also gaben wir ihnen viel kleinere Workloads. Wenn wir 1992 eine MP3-Datei abspielten, wussten wir, dass wir möglicherweise mehr als die Hälfte der CPU für diese Aufgabe einsetzen müssen.
Qwertie
6
mp3 stammt aus dem Jahr 1995. 92 hatten wir kaum 386, auf keinen Fall konnten sie eine mp3 abspielen, und 50% der CPU-Zeit setzen ein gutes Multitask-Betriebssystem, einen Leerlaufprozess und einen präventiven Scheduler voraus. Nichts davon gab es zu diesem Zeitpunkt auf dem Verbrauchermarkt. Es war 100% von dem Moment an, als die Stromversorgung eingeschaltet war, Ende der Geschichte.
v.oddou
7

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.

Jason S.
quelle
1
Es heißt parametrischer Polymorphismus
tjysdsg
4

Ja, Sie haben Recht, und wenn Sie neugierig auf die Kosten eines virtuellen Funktionsaufrufs sind, finden Sie diesen Beitrag möglicherweise interessant.

Serge
quelle
1
Der verlinkte Artikel berücksichtigt keinen sehr wichtigen Teil des virtuellen Anrufs, und dies ist eine mögliche Fehlvorhersage für Zweige.
Suma
4

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".

Daemin
quelle
2
Wenn Sie etwas in einer engen Schleife aufrufen, bleibt der gesamte Code und die Daten wahrscheinlich im Cache ...
Greg Rogers
2
Ja, aber wenn diese rechte Schleife eine Liste von Objekten durchläuft, kann jedes Objekt möglicherweise über denselben Funktionsaufruf eine virtuelle Funktion an einer anderen Adresse aufrufen.
Daemin
3

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:

#include <time.h>

class Direct
{
public:
    int Perform(int &ia) { return ++ia; }
};

class AbstrBase
{
public:
    virtual int Perform(int &ia)=0;
};

class Derived: public AbstrBase
{
public:
    virtual int Perform(int &ia) { return ++ia; }
};


int main(int argc, char* argv[])
{
    Direct *pdir, dir;
    pdir = &dir;

    int ia=0;
    double start = clock();
    while( pdir->Perform(ia) );
    double end = clock();
    printf( "Direct %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    Derived drv;
    AbstrBase *ab = &drv;

    ia=0;
    start = clock();
    while( ab->Perform(ia) );
    end = clock();
    printf( "Virtual: %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    return 0;
}

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.

Evgueny Sedov
quelle
4
Ein virtueller Funktionsaufruf ist im Vergleich zu teuer ++ia. Na und?
Bo Persson
2

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.

Es ist Pete
quelle
2

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
1

Eine Sache zu beachten ist, dass dies:

boolean contains(A element) {
    for (A current: this)
        if (element.equals(current))
            return true;
    return false;
}

kann schneller sein als dies:

boolean contains(A element) {
    for (A current: this)
        if (current.equals(equals))
            return true;
    return false;
}

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.

nikdeapen
quelle
0

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
2
Niemals, egal wie klein der Zielcomputer ist?
zumalifeguard
Ich hätte vielleicht zugestimmt, wenn Sie das so formuliert hätten: 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 ist sometimes, nicht zu sagen never.
underscore_d
-1

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.

// g++ -std=c++0x -o perf perf.cpp -lrt
#include <typeinfo>    // typeid
#include <cstdio>      // printf
#include <cstdlib>     // atoll
#include <ctime>       // clock_gettime

struct Virtual { virtual int call() { return 42; } }; 
struct Inline { inline int call() { return 42; } }; 
struct Normal { int call(); };
int Normal::call() { return 42; }

template<typename T>
void test(unsigned long long count) {
    std::printf("Timing function calls of '%s' %llu times ...\n", typeid(T).name(), count);

    timespec t0, t1;
    clock_gettime(CLOCK_REALTIME, &t0);

    T test;
    while (count--) test.call();

    clock_gettime(CLOCK_REALTIME, &t1);
    t1.tv_sec -= t0.tv_sec;
    t1.tv_nsec = t1.tv_nsec > t0.tv_nsec
        ? t1.tv_nsec - t0.tv_nsec
        : 1000000000lu - t0.tv_nsec;

    std::printf(" -- result: %d sec %ld nsec\n", t1.tv_sec, t1.tv_nsec);
}

template<typename T, typename Ua, typename... Un>
void test(unsigned long long count) {
    test<T>(count);
    test<Ua, Un...>(count);
}

int main(int argc, const char* argv[]) {
    test<Inline, Normal, Virtual>(argc == 2 ? atoll(argv[1]) : 10000000000llu);
    return 0;
}

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.

christianparpart
quelle
12
Ich denke, es ist sehr wahrscheinlich, dass Ihr Compiler erkennen kann, dass der virtuelle Funktionsaufruf in Ihrem Code nur Virtual :: call aufrufen kann. In diesem Fall kann es einfach inline sein. Es gibt auch nichts, was den Compiler daran hindert, Normal :: call einzubinden, obwohl Sie nicht darum gebeten haben. Ich denke also, dass es durchaus möglich ist, dass Sie für die 3 Operationen die gleichen Zeiten erhalten, da der Compiler identischen Code für sie generiert.
Bjarke H. Roune