C ++ - Tabellenspeicherkosten für virtuelle Funktionen

9

Überlegen Sie:

%Vor%

Ich mache jetzt:

%Vor%

Wenn ich sizeof(B) aufrufe, ist die zurückgegebene Größe die Größe, die von den drei doppelten Membern benötigt wird, plus etwas Overhead, der für die virtuelle Funktionszeigertabelle erforderlich ist. Jetzt, zurück zu meinem Code, stellt sich heraus, dass 'sizeof (myclass)' 32 ist; das heißt, ich verwende 24 Bytes für meine Datenmitglieder und 8 Bytes für die virtuelle Funktionstabelle (4 virtuelle Funktionen). Meine Frage ist: Kann ich das irgendwie straffen? Mein Programm wird schließlich eine Menge Speicher verwenden, und ich mag nicht den Klang von 25% davon von virtuellen Funktionen Zeiger gegessen werden.

    
Stewart 26.10.2009, 17:55
quelle

13 Antworten

39

Die v-Tabelle ist pro Klasse und nicht pro Objekt. Jedes Objekt enthält nur einen -Zeiger für seine V-Tabelle. Der Overhead pro Instanz ist also sizeof(pointer) (normalerweise 4 oder 8 Bytes). Es spielt keine Rolle, wie viele virtuelle Funktionen Sie für die Größe des Klassenobjekts haben. In Anbetracht dessen denke ich, du solltest dir nicht zu viele Sorgen machen.

    
Ponting 26.10.2009 18:02
quelle
10

Normalerweise wird bei jeder Instanz einer Klasse mit mindestens einer virtuellen Funktion ein zusätzlicher Zeiger mit seinen expliziten Datenelementen gespeichert.

Es gibt keinen Weg dazu, aber denken Sie daran, dass jede virtuelle Funktionstabelle (wiederum typischerweise) zwischen allen Instanzen der Klasse geteilt wird. Es gibt also keinen großen Aufwand für mehrere virtuelle Funktionen oder zusätzliche Vererbungsebenen, nachdem Sie bezahlt haben die "vptr tax" (kleine Kosten des vtable pointer).

Bei größeren Klassen wird der Overhead prozentual viel kleiner.

Wenn Sie eine Funktionalität wünschen, die etwas mit virtuellen Funktionen zu tun hat, müssen Sie dafür etwas bezahlen. Tatsächlich kann die Verwendung nativer virtueller Funktionen die günstigste Option sein.

    
Charles Bailey 26.10.2009 18:02
quelle
6

Sie haben zwei Möglichkeiten.

1) Mach dir keine Sorgen.

2) Verwenden Sie keine virtuellen Funktionen. Wenn Sie jedoch keine virtuellen Funktionen verwenden, können Sie die Größe einfach in Ihren Code verschieben, da Ihr Code komplexer wird.

    
Roland Rabien 26.10.2009 18:02
quelle
6

Die Raumkosten einer Vtable sind ein Zeiger (Modulo-Ausrichtung). Die Tabelle selbst wird nicht in jede Instanz der Klasse eingefügt.

    
Nikolai Fetissov 26.10.2009 18:03
quelle
5

Weg vom Nicht-Problem des vtable-Zeigers in Ihrem Objekt:

Ihr Code hat andere Probleme:

%Vor%

Das Problem, das Sie haben, ist das Schneideproblem.
Sie können ein Objekt der Klasse B nicht in ein Leerzeichen einfügen, das für ein Objekt der Klasse A reserviert ist Sie werden nur den B- (oder C-) Teil des Objekts abschneiden und nur den A-Teil übriglassen.

Was Sie tun möchten. Ist ein Array von A-Zeigern, so dass es jedes Element nach Zeiger halten.

%Vor%

Jetzt haben Sie ein anderes Problem der Zerstörung. OK. Das könnte ewig so weitergehen.
Kurze Antwort verwenden boost: ptr_vector & lt; & gt;

%Vor%

Ordnen Sie Array nie so zu, es sei denn, Sie müssen es (Sein zu Java, um nützlich zu sein).

    
Martin York 26.10.2009 19:34
quelle
2

Wie viele Instanzen von A-abgeleiteten Klassen erwarten Sie? Wie viele verschiedene A-abgeleitete Klassen erwarten Sie?

Beachten Sie, dass wir selbst bei einer Million Instanzen von insgesamt 32 MB sprechen. Bis zu 10 Millionen, verschwitze es nicht.

Im Allgemeinen benötigen Sie einen zusätzlichen Zeiger pro Instanz (wenn Sie auf einer 32-Bit-Plattform arbeiten, sind die letzten 4 Byte aufgrund der Ausrichtung erforderlich). Jede Klasse verbraucht zusätzliche (Number of virtual functions * sizeof(virtual function pointer) + fixed size) Bytes für ihre VMT.

Beachten Sie, dass bei Berücksichtigung der Ausrichtung für die Doubles sogar ein einzelnes Byte als Typbezeichner die Array-Elementgröße auf 32 erhöht. Daher ist die Lösung von Stjepan Rajko in einigen Fällen hilfreich, in Ihrem Fall jedoch nicht.

Vergessen Sie auch nicht den Overhead eines allgemeinen Heaps für so viele kleine Objekte. Sie können weitere 8 Bytes pro Objekt haben. Mit einem benutzerdefinierten Heap-Manager - wie einem objekt- / größenspezifischen Pool-Allokator - können Sie das tun Sparen Sie hier mehr und verwenden Sie eine Standardlösung.

    
peterchen 26.10.2009 18:51
quelle
2

Wenn Sie Millionen von diesen Dingen haben werden, und die Erinnerung eine ernsthafte Sorge für Sie ist, dann sollten Sie sie wahrscheinlich nicht zu Objekten machen. Erklären Sie sie einfach als eine Struktur oder ein Array von 3 Doppel (oder was auch immer), und setzen Sie die Funktionen, um die Daten woanders zu manipulieren.

Wenn Sie das polymorphe Verhalten wirklich brauchen, können Sie wahrscheinlich nicht gewinnen, da die Typinformationen, die Sie in Ihrer Struktur speichern müssen, einen ähnlichen Platz einnehmen werden ...

Ist es wahrscheinlich, dass Sie große Gruppen von Objekten vom selben Typ haben? In diesem Fall könnten Sie die Typinformationen aus den einzelnen "A" -Klassen um eine Stufe "nach oben" legen ...

Etwas wie:

%Vor%     
Mark Bessey 26.10.2009 19:13
quelle
1

Wenn Sie alle abgeleiteten Typen und ihre jeweiligen Aktualisierungsfunktionen im Voraus kennen, können Sie den abgeleiteten Typ in A speichern und den manuellen Versand für die Aktualisierungsmethode implementieren.

Wie jedoch andere aufzeigen, bezahlen Sie für die vtable nicht wirklich viel, und der Kompromiss besteht in der Komplexität des Codes (und je nach Ausrichtung sparen Sie möglicherweise überhaupt keinen Speicher!). Wenn eines Ihrer Datenelemente einen Destruktor hat, müssen Sie sich auch darum sorgen, den Destruktor manuell zu verteilen.

Wenn Sie diese Route immer noch verwenden möchten, könnte es so aussehen:

%Vor%     
Stjepan Rajko 26.10.2009 18:37
quelle
1

Sie fügen jeder vTable für jedes Objekt einen einzelnen Zeiger hinzu - wenn Sie mehrere neue virtuelle Funktionen hinzufügen, wird die Größe der einzelnen Objekte nicht erhöht. Beachten Sie, dass selbst bei einer 32-Bit-Plattform, bei der Zeiger 4 Byte groß sind, die Größe des Objekts um 8 erhöht wird, wahrscheinlich aufgrund der allgemeinen Ausrichtungsanforderungen der Struktur (dh Sie erhalten 4 Byte der Auffüllung).

Selbst wenn Sie die Klasse nicht virtuell gemacht haben, würde das Hinzufügen eines einzelnen Char-Members wahrscheinlich ganze 8 Bytes zur Größe jedes Objekts hinzufügen.

Ich denke, dass Sie die Größe Ihrer Objekte nur auf folgende Weise reduzieren können:

  • mache sie nicht virtuell (du brauchst wirklich polymorphes Verhalten?)
  • verwenden Sie Floats anstelle von double für einen oder mehrere Datenelemente, wenn Sie die Genauigkeit nicht benötigen
  • Wenn Sie wahrscheinlich viele Objekte mit denselben Werten für die Datenelemente sehen, können Sie Speicherplatz sparen, wenn Sie die Objekte mithilfe der Flyweight Design-Muster
Michael Burr 26.10.2009 19:01
quelle
1

Keine direkte Antwort auf die Frage, aber auch, dass die Deklarationsreihenfolge Ihrer Datenmitglieder Ihren realen Speicherverbrauch pro Klassenobjekt erhöhen oder verringern kann. Dies liegt daran, dass die meisten Compiler die Reihenfolge, in der die Klassenmitglieder im Speicher angeordnet sind, nicht optimieren können (also nicht: lesen), um die interne Fragmentierung aufgrund von Ausrichtungsfehlern zu verringern.

    
Chris Tonkinson 26.10.2009 19:10
quelle
1

Wie bereits erwähnt, wird in einem typischen populären Implementierungsansatz, sobald eine Klasse polymorph wird, jede Instanz um die Größe eines gewöhnlichen Datenzeigers größer. Es spielt keine Rolle, wie viele virtuelle Funktionen Sie in Ihrer Klasse haben. Auf einer 64-Bit-Plattform würde die Größe um 8 Byte zunehmen. Wenn Sie das 8-Byte-Wachstum auf einer 32-Bit-Plattform beobachtet haben, könnte dies durch das Auffüllen des 4-Byte-Zeigers für die Ausrichtung verursacht worden sein (wenn Ihre Klasse eine 8-Byte-Ausrichtung erfordert).

Zusätzlich ist es wahrscheinlich bemerkenswert, dass virtuelle Vererbung zusätzliche Datenzeiger in Klasseninstanzen (virtuelle Basiszeiger) einfügen kann. Ich bin nur mit ein paar Implementierungen vertraut und in mindestens einer war die Anzahl der virtuellen Basiszeiger identisch mit der Anzahl der virtuellen Basen in der Klasse, was bedeutet, dass virtuelle Vererbung potenziell mehrere interne Datenzeiger zu jeder Instanz hinzufügen kann. p>     

AnT 26.10.2009 21:49
quelle
0

Angesichts all der Antworten, die bereits hier sind, denke ich, dass ich verrückt sein muss, aber das scheint mir richtig zu sein, also poste ich es trotzdem. Als ich das Codebeispiel zum ersten Mal gesehen habe, dachte ich, du würdest die Instanzen von B und C schneiden, aber dann sah ich ein wenig genauer hin. Ich bin jetzt ziemlich sicher, dass Ihr Beispiel überhaupt nicht kompiliert wird, aber ich habe keinen Compiler für diese Box zum Testen.

%Vor%

Für mich sieht das so aus, als ob die erste Zeile ein Array von 1000 A zuweist. Die nachfolgenden zwei Zeilen arbeiten jeweils mit den ersten und zweiten Elementen dieses Arrays, die Instanzen von A sind, nicht Zeiger auf A . Daher können Sie diesen Elementen keinen Zeiger auf A zuweisen (und new B() gibt einen solchen Zeiger zurück). Die Typen sind nicht gleich, daher sollte es bei der Kompilierung fehlschlagen (es sei denn, A hat einen Zuweisungsoperator, der ein A* übernimmt, in diesem Fall wird er tun, was immer Sie ihm zu tun gaben).

Also, bin ich völlig außer Reichweite? Ich freue mich darauf herauszufinden, was ich verpasst habe.

    
rmeador 26.10.2009 19:28
quelle
-1

Wenn Sie wirklich Speicher des virtuellen Tabellenzeigers in jedem Objekt speichern möchten, können Sie Code im C-Stil implementieren ...

z. B.

%Vor%     
Ashish 27.10.2009 12:10
quelle

Tags und Links