Eine Standardmethode zur Vermeidung von virtuellen Funktionen

8

Ich habe eine Bibliothek, in der es viele kleine Objekte gibt, die jetzt alle virtuelle Funktionen haben. Es geht so weit, dass die Größe des Zeigers auf eine virtuelle Funktionstabelle die Größe der nützlichen Daten im Objekt übersteigen kann (es kann oft nur eine Struktur mit einem einzelnen float darin sein). Die Objekte sind Elemente in einer numerischen Simulation in einem dünn besetzten Graphen und können daher nicht leicht zusammengeführt werden (etc.).

Ich mache mir weniger Gedanken über die Kosten des virtuellen Funktionsaufrufs als vielmehr über die Kosten des Speichers . Was passiert, ist, dass der Zeiger auf die virtuelle Funktionstabelle grundsätzlich die Effizienz des Caches reduziert. Ich frage mich, ob ich besser wäre mit einer Typ-ID als Ganzzahl anstelle der virtuellen Funktion gespeichert.

Ich kann keinen statischen Polymorphismus verwenden, da sich alle meine Objekte in einer einzigen Liste befinden. Ich muss Operationen für Elemente ausführen können, die von einem Index ausgewählt werden (was ein Laufzeitwert ist - daher gibt es keinen statischen Weg) Bestimmen Sie den Typ).

Die Frage ist: Gibt es ein Entwurfsmuster oder einen allgemeinen Algorithmus, der eine Funktion von einer Schnittstelle dynamisch aufrufen kann, wenn eine Liste von Typen (z. B. in einer Typliste) und ein Typindex?

angegeben ist

Die Schnittstelle ist definiert und ändert sich nicht viel, aber neue Objekte werden in Zukunft von (möglicherweise weniger qualifizierten) Benutzern der Bibliothek deklariert und es sollte kein großer Aufwand dafür erforderlich sein. Leistung ist von größter Bedeutung. Leider keine C ++ 11.

Bisher habe ich vielleicht einen dummen Proof of Concept:

%Vor%

Ich habe noch nicht in die Baugruppe geschaut, aber ich glaube, wenn man sie statisch macht, könnte die Erstellung des Funktionszeiger-Arrays praktisch kostenlos gemacht werden. Eine andere Alternative ist die Verwendung eines binären Suchbaums, der in der Typliste generiert wird und das Inlining ermöglichen würde.

    
the swine 13.05.2014, 09:55
quelle

2 Antworten

4

Ich benutzte das Thunk-Table-Konzept, das ich in der Frage skizziert hatte. Für jede Operation gibt es eine einzige Instanz einer Thunk-Tabelle (die statisch ist und über eine Vorlage gemeinsam genutzt wird). Der Compiler stellt daher automatisch sicher, dass es nur eine einzige Tabelleninstanz pro Operationstyp gibt, nicht pro Aufruf. So haben meine Objekte überhaupt keine virtuellen Funktionen .

Am wichtigsten ist, dass der Geschwindigkeitsgewinn durch die Verwendung eines einfachen Funktionszeigers anstelle von virtuellen Funktionen vernachlässigbar ist (aber auch nicht langsamer). Viel Schnelligkeit bringt die Implementierung eines Entscheidungsbaums und die statische Verknüpfung aller Funktionen - dadurch wurde die Laufzeit einiger nicht sehr rechenintensiver Codes um etwa 40% verbessert.

Ein interessanter Nebeneffekt ist die Möglichkeit, "virtuelle" Template-Funktionen zu haben, was normalerweise nicht möglich ist.

Ein Problem, das ich lösen musste, war, dass alle meine Objekte eine Schnittstelle haben mussten, da sie auf andere Aufrufe als die Funktoren zugreifen würden. Ich habe dafür eine freistehende Fassade entworfen. Eine Fassade ist eine virtuelle Klasse, die die Schnittstelle der Objekte deklariert. Eine distanzierte Fassade ist eine Instanz dieser virtuellen Klasse, die auf eine bestimmte Klasse spezialisiert ist (für alle in der Liste gibt operator [] eine freigestellte Fassade für den Typ des ausgewählten Elements zurück).

%Vor%

Um dies zu verwenden, muss man Folgendes tun:

%Vor%

Dies ermöglicht mir, die optimierte Thunk-Tabelle im Hochleistungsteil des Codes zu verwenden und immer noch Objekte mit polymorphem Verhalten zu verwenden, um diese bequem handhaben zu können, wenn keine Leistung benötigt wird.

    
the swine 03.08.2014, 16:30
quelle
0

CRTP ist eine Kompilierzeit-Alternative zu virtuellen Funktionen:

%Vor%

Es beruht auf der Tatsache, dass die Definition des Members erst beim Aufruf instanziiert wird. So sollte Base auf jedes Mitglied von Derived nur in der Definition seiner Member-Funktionen verweisen, niemals in Prototypen oder Datenmembern

    
apoorvkul 13.05.2014 10:40
quelle