Template-Bloom mit Vererbung reduzieren

8

Hat jemand Erfahrung damit, den Vorlagencode durch Vererbung zu reduzieren?

Ich zögere, unsere Container auf diese Weise neu zu schreiben:

%Vor%

Ich sollte maximale Leistung beibehalten und gleichzeitig die Kompilierzeit reduzieren.

Ich frage mich auch, warum stl-Implementierungen diesen Ansatz nicht verwenden

Danke für Ihre Rückmeldungen

    
benoitj 14.06.2010, 13:07
quelle

7 Antworten

3

Nur sehr wenige Operationen auf einem Vektor sind sinnvoll, wenn Sie nicht wissen, um welche Art von gespeicherten Elementen es sich handelt. Zum Beispiel muss die clear() -Methode, die Sie zu Ihrer Basisklasse hinzugefügt haben, die Destruktoren der Elemente aufrufen, die aus dem Vektor entfernt wurden, so dass sie ihren Typ kennen muss und eine Templatestruktur benötigt.

Es gibt auch wirklich nicht viel, was du mit der a void *m_rawData machen kannst, ohne die Arten der Dinge darin zu kennen, im Grunde müssen alle Operationen darauf wenigstens die Größe des gespeicherten Typs kennen. Das einzige, was mir einfällt, ist, dass Sie free() it haben können, wenn Sie wissen, dass es keine Elemente enthält (wenn es Elemente enthält, müssen Sie deren Destruktoren aufrufen). Das Zuweisen, Festlegen und Zugreifen auf Elemente funktioniert nicht, wenn Sie nicht wissen, wo die einzelnen Elemente beginnen und enden. Auch die Implementierung aller Methoden wäre viel sauberer und einfacher, wenn m_rawData stattdessen eine korrekt eingegebene T* wäre.

Eine size() -Methode in der Basisklasse würde nur funktionieren, wenn ihre einzige Aufgabe darin besteht, eine m_size -Membervariable zurückzugeben, aber ein Vektor muss die Größe nicht unbedingt explizit speichern (die Implementierungen, die ich kenne, t). Sie könnten wahrscheinlich implementieren ist, so dass die Größe explizit gespeichert wird, aber dann wieder size() ist wahrscheinlich keine Methode, die lange dauert, um zu kompilieren, auch wenn es templated ist.

Insgesamt glaube ich nicht, dass es noch viele Methoden gibt, die in einer Basisklasse implementiert werden können. Die meisten Operationen auf einem Vektor müssen über die darin gespeicherten Elemente wissen.

    
sth 14.06.2010 13:42
quelle
1

Ich denke, das ist eine vorzeitige Optimierung. Im Allgemeinen, außer in eingebetteten Systemen, sind Speicherplatz und Speicher reichlich und billig, so dass es keinen Grund gibt, zu versuchen, für eine kleine Menge an Coderaum zu optimieren. Indem man alles im Template-Code hält, macht es mehr offensichtlich, was vor sich geht, als Vererbung zu verwenden, was die Dinge komplizierter machen würde.

Außerdem werden die meisten Anwendungen nicht Hunderte von Instanziierungen erzeugen, und für jedes T können nicht alle Methoden verwendet werden, wodurch der Code-Footprint weiter reduziert wird.

Nur wenn es extrem enge Speicherüberlegungen (eingebettet) gäbe, würde ich verschiedene mögliche Ansätze in Betracht ziehen (einschließlich der, die Sie vorgestellt haben).

EDIT: Ich bin mir nicht sicher, ob man in ein paar Standard-Container-Cases viel gewinnen kann, da sie immer noch viel Template-Code benötigen. Für interne Klassen, die nur wenig Template-spezifischen Code und viel gemeinsame Logik haben, könnte dies sowohl dem generierten Code als auch der Kompilierungsgeschwindigkeit helfen. Ich vermute, dass es nicht oft verwendet wird, weil es komplexer ist und die Vorteile auf bestimmte Szenarien beschränkt sind.

    
Mark B 14.06.2010 13:15
quelle
1

Ich verstehe Ihren Ansatz.

Um ehrlich zu sein, ich habe es benutzt ... obwohl offensichtlich nicht für STL-Container: Ihr Code ist praktisch fehlerfrei und optimiert und es ist sehr unwahrscheinlich, dass ich eine bessere Implementierung selbst entwickeln kann!

Ich kümmere mich nicht viel um die Kompilierzeit: es ist ein peinlich paralleles Problem (abgesehen von Link) und distcc usw. kümmern sich um alle Probleme, die Sie selbst mit einer großen Codebasis haben können. Und ich meine groß, ich arbeite in einer Firma, die einen neuen Compiler von HP benötigt, weil die Version, die wir hatten, nicht mehr als 128Ko ... in der Befehlszeile des Linkers unterstützt hat. Und es war nur eine der Anwendungen, und es war vor ein paar Jahren, und sie haben es glücklicherweise seither in mehreren Brocken aufgeteilt.

So sehr mir die Kompilierzeit aber auch egal ist, ich mache viel auf die reduzierten Abhängigkeiten und die binäre Kompatibilität aus. Und wenn ich selbst einen Vorlagencode schreibe, überprüfe ich, ob es möglich ist, einige Operationen außerhalb des Vorlagencodes zu berücksichtigen.

Die erste Aufgabe besteht darin, diejenigen Punkte zu isolieren, an denen Sie wirklich gewinnen können. Eine Codezeile zu bekommen ist nicht die Zeit wert, Sie wollen volle Funktionen bekommen.

Die zweite Aufgabe besteht darin, zu entscheiden, ob Sie sie inline halten möchten oder nicht. Es hängt davon ab, ob Ihnen die Leistung wichtig ist oder nicht. Der Overhead eines Funktionsaufrufs kann für Sie wichtig sein oder auch nicht.

Allerdings würde ich auf keinen Fall Vererbung für den Job verwenden. Inheritance ist eine IS-A -Verbindung: Sie definiert eine Schnittstelle, keine Implementierung. Verwenden Sie entweder Composition oder einfach freie Funktionen, die Sie in einem Dienstprogramm-Namespace ( detail wie in Boost?) Speichern.

    
Matthieu M. 14.06.2010 18:16
quelle
0

IIRC, Qt verwendet (oder verwendet?) einen ähnlichen Ansatz für ihre QList et al.

Grundsätzlich würde es funktionieren, aber Sie müssen sicherstellen, dass Sie alles, was von T abhängt, in die Vektorvorlage einfügen. Leider ist dies fast der gesamte Code in der Vektorklasse (in vielen Fällen muss der Code einige T -Konstruktor oder -Destruktor aufrufen), mit Ausnahme der Zuweisung von Rohspeicher und size() / capacity() . Ich bin mir nicht sicher, ob es sich auszahlt, also überprüfe es.

Es zahlt sich aus, wenn man von einem Template-Parameter abstrahieren kann (zB set<T>::iterator braucht den Komparator des Sets nicht kennen) oder wenn man eine vollständige Implementierung für eine große Klasse von Typen brauen kann (zB mit trivial copy-ctor und dtor).

    
jpalecek 14.06.2010 13:23
quelle
0

Der Code, den Sie gepostet haben, ist einfach falsch. Wenn die Klasse, die Sie im Vektor speichern, einen Destruktor hat, wird dieser Destruktor nicht aufgerufen, weil der Compiler vectorBase alle Informationen darüber verloren hat, wann der Destruktor durch% cas_de% aufgerufen werden soll.

Um dies richtig auszuführen, müssen Sie beim Aufruf des richtigen Destruktors verschiedene Kopien des Codes generieren, die jeweils den richtigen Destruktor aufrufen. Diese Arbeit wird durch die Verwendung von Vorlagen vereinfacht.

(Um Ihren Ansatz mit einer Nicht-Template-Basisklasse zu verwenden, müssen Sie genauso viel Maschinencode generieren, aber Sie müssen sehr viel mehr C ++ - Code von Hand schreiben.)

Deshalb ist dieser Ansatz wirklich nicht lohnenswert.

    
Ken Bloom 14.06.2010 13:37
quelle
0

Kurz gesagt:

Ja, dieser Ansatz wird [wahrscheinlich] in begrenzten, spezialisierten Umständen funktionieren. Ich vermute nicht, dass std::vector (oder der Rest von STL) zu diesen Umständen gehören.

Das lange:

Wie andere bereits erwähnt haben (und ich stimme zu), ist Code Bloat außerhalb eines eingebetteten Systems kein großes Problem für eine kompilierte Binärdatei.

Aber viele von uns leiden unter den Kompilierungskosten, indem wir während des Kompilierungsschritts mehr Code erzeugen, als wenn wir kompilierte Bibliotheken hätten, die verlinkt werden könnten (anstatt Headerdateien zu kompilieren). Fügen Sie ihm die Schwierigkeit hinzu, eine dieser Template-Header-Dateien zu ändern und das gesamte Projekt von Grund auf neu zu kompilieren. Lange Übersetzungszeiten machen traurige Entwickler: (

Es kann keinen großen Prozentsatz der Entwickler beeinträchtigen - abhängig von der Größe der Codebasis Ihres Unternehmens und davon, wie Sie Ihre Build-Umgebung strukturieren. Es belastet uns in meiner Firma.

Wie einige Antworten darauf hingewiesen haben, gibt es nicht viel zu abstrahieren von einem std::vector , das es in Ihrem Beispiel besser machen würde. Sicherlich müssen Sie in der Lage sein, einzelne Elemente zu erstellen und zu zerstören, und jede Methode virtual würde die Laufzeitleistung beeinträchtigen (was wichtiger ist als die Kompilierungszeit). Darüber hinaus verliert der Compiler die Möglichkeit, die Bibliothek void* für Vorlagencode zu optimieren, was zu einem großen Leistungsverlust führen kann.

Ich interessiere mich mehr für die komplexeren Strukturen - würde std::map von der Abstraktion profitieren? Was wäre, wenn Sie den Rot-Schwarz-Baum (die SGI-Implementierung) herausnehmen und mit einer rot-schwarzen Baumbibliothek verknüpfen würden? Sie würden (wahrscheinlich) die Elemente außerhalb von std::map speichern müssen, so dass die Destruktoren nicht aufgerufen werden müssen, aber dies (wiederum) zu einem Leistungsverlust aufgrund der Verdoppelung der Indirektion führen kann.

>

Ich bin ziemlich sicher, dass Sie diese Methode nicht verwenden können, um die Implementierung von STL zu verbessern.

Wenn Sie die Datenstrukturen, die Sie gespeichert haben, besser kennen, oder wenn Sie einen sehr spezifischen Vorlagen-Typ hatten (nicht unbedingt einen Container), könnten Sie wahrscheinlich Ihre Leistung verbessern. Aber dann stellt sich die Frage: Wie oft werden Sie diesen neuen Templat-Typ verwenden, so dass der Aufwand für die Erstellung deutlich erhöht wird? Sicher würde es helfen, Zeiten für std::vector zu kompilieren, aber vielleicht nicht für my_domain_specific_ptr_container . Wenn Sie 50% der Kompilierzeit für my_domain_specific_ptr_container speichern, wie oft müssten Sie es verwenden, um einen signifikanten Build-Boost zu bemerken, um die zusätzliche Komplexität der Klasse zu rechtfertigen (und die Debug-Fähigkeit zu reduzieren).

Wenn Sie es nicht schon getan haben, ist es vielleicht besser, Ihre Build-Tools zu verteilen:)

Wenn Sie dies dagegen versuchen und es für STL-Container funktioniert, schreiben Sie bitte zurück. Ich möchte einen schnelleren Build! ;)

    
Stephen 14.06.2010 15:33
quelle
0

Einige Implementierungen tun den obigen Ansatz (eine Form von). Hier ist GCC

%Vor%

In diesem Fall besteht das Ziel darin, die Speicherverwaltung an _Vector_base zu delegieren. Wenn Sie sich dafür entscheiden, Ihre Zeit damit zu verbringen, STL neu zu erfinden, folgen Sie bitte hier Ihren Ergebnissen. Vielleicht helfen Ihre Bemühungen, den alten "Code Bloat" -Rufen, die immer noch von Zeit zu Zeit zu hören sind, ein Ende zu setzen.

    
John 15.06.2010 01:46
quelle