Gibt es einen Arbeitsspeicher-Overhead, der mit Heap-Speicherzuordnungen verknüpft ist (z. B. Marker im Heap)?

8

Ich denke insbesondere an C ++ unter Windows mit einem neueren C ++ - Compiler von Visual Studio. Ich wundere mich über die Heap-Implementierung:

Wenn ich davon ausgehe, dass ich den Release-Compiler verwende und ich mich nicht mit Speicherfragmentierungs- / Packproblemen befasse, gibt es einen Speicheraufwand, der mit der Zuweisung von Speicher auf dem Heap verbunden ist? Wenn ja, wie viele Bytes pro Zuweisung könnte das sein? Wäre es im 64-Bit-Code größer als 32-Bit?

Ich weiß nicht wirklich viel über moderne Heap-Implementierungen, aber ich frage mich, ob es Marker gibt, die mit jeder Zuweisung in den Heap geschrieben werden, oder ob irgendeine Art von Tabelle gepflegt wird (wie eine Dateizuordnungstabelle).

>

Zu einem verwandten Punkt (weil ich in erster Linie über Standardbibliotheksfunktionen wie 'map' nachdenke), verwendet die Microsoft-Standardbibliotheksimplementierung jemals einen eigenen Zuordner (für Dinge wie Baumknoten), um die Heapnutzung zu optimieren ?

    
Coder_Dan 08.04.2013, 14:02
quelle

3 Antworten

7

Ja, absolut.

Jeder zugewiesene Speicherblock hat einen konstanten Overhead eines "Headers" sowie einen kleinen variablen Teil (typischerweise am Ende). Wie viel genau das ist, hängt von der genauen verwendeten C-Laufzeitbibliothek ab. In der Vergangenheit habe ich experimentell gefunden, dass es ungefähr 32-64 Bytes pro Zuweisung ist. Der variable Teil soll mit der Ausrichtung fertig werden - jeder Speicherblock wird auf eine nette gerade 2 ^ n Basisadresse ausgerichtet sein - typischerweise 8 oder 16 Bytes.

Ich bin mir nicht sicher, wie das interne Design von std::map oder ähnlich funktioniert, aber ich bezweifle sehr, dass es dort spezielle Optimierungen gibt.

Sie können den Overhead ganz einfach testen mit:

%Vor%

[Anmerkung zu den Pedanten, die wahrscheinlich die meisten Stammkunden hier sind, der obige a-b-Ausdruck ruft undefiniertes Verhalten auf, da die Subtraktion der Adresse einer zugewiesenen und der Adresse einer anderen nicht definiert ist. Dies ist mit Maschinen zu bewältigen, die keine linearen Speicheradressen haben, z. segmentierter Speicher oder "verschiedene Arten von Daten werden an Standorten basierend auf ihrem Typ gespeichert". Das obige sollte definitiv auf jedem x86-basierten Betriebssystem funktionieren, das kein segmentiertes Speichermodell mit mehreren Datensegmenten für den Heap verwendet - was bedeutet, dass es für Windows und Linux im 32- und 64-Bit-Modus sicher funktioniert.

Vielleicht möchten Sie es mit verschiedenen Typen ausführen - denken Sie daran, dass das Diff in "Nummer des Typs ist. Wenn Sie es also machen int *a, *b wird in" vier Bytes Einheiten "sein. Sie könnten ein% machen co_de%

[diff kann negativ sein, und wenn Sie dies in einer Schleife ausführen (ohne reinterpret_cast<char*>(a) - reinterpret_cast<char *>(b); und a zu löschen), können Sie plötzliche Sprünge feststellen, bei denen ein großer Speicherbereich erschöpft ist und die Laufzeitbibliothek einen anderen großen Speicher zugewiesen hat blockieren]

    
Mats Petersson 08.04.2013, 14:14
quelle
4

Visual C ++ bettet Steuerinformationen (Links / Größen und möglicherweise einige Prüfsummen) in der Nähe der Grenzen zugeordneter Puffer ein. Dies hilft auch, Pufferüberläufe während der Speicherzuweisung und Freigabe aufzuheben.

Darüber hinaus sollten Sie sich daran erinnern, dass malloc() Zeiger liefert, die für alle grundlegenden Typen ( char , int , long long , double , void* , void(*)() ) passend ausgerichtet sind Diese Ausrichtung ist typischerweise von der Größe des größten Typs, also könnte sie 8 oder sogar 16 Bytes sein. Wenn Sie ein einzelnes Byte zuweisen, können 7 bis 15 Byte nur für die Ausrichtung verloren gehen. Ich bin nicht sicher, ob operator new das gleiche Verhalten hat, aber es kann sehr gut der Fall sein.

Das sollte dir eine Idee geben. Der genaue Speicherverbrauch kann nur aus der Dokumentation (falls vorhanden) oder dem Test ermittelt werden. Der Sprachstandard definiert es in keiner Weise.

    
Alexey Frunze 08.04.2013 14:16
quelle
2

Ja. Alle praktischen dynamischen Speicherzuordner haben eine minimale Granularität 1 . Wenn die Granularität beispielsweise 16 Byte beträgt und Sie nur 1 Byte anfordern, werden die gesamten 16 Byte dennoch zugewiesen. Wenn Sie nach 17 Bytes fragen, wird ein Block mit einer Größe von 32 Bytes usw. zugewiesen ...

Es gibt auch ein (verwandtes) Problem der Ausrichtung. 2

Eine ganze Reihe von Allokatoren scheint eine Kombination aus einer Größenkarte und freien Listen zu sein - sie teilen potenzielle Allokationsgrößen in "Buckets" auf und führen für jede von ihnen eine eigene freie Liste. Werfen Sie einen Blick auf Doug Leas Malloc . Es gibt viele andere Zuweisungstechniken mit verschiedenen Kompromissen, aber das geht hier über den Rahmen hinaus ...

1 Typischerweise 8 oder 16 Bytes. Wenn der Zuordner eine freie Liste verwendet, muss er zwei Zeiger in jedem freien Steckplatz codieren, so dass ein freier Steckplatz nicht kleiner als 8 Byte (bei 32 Bit) oder 16 Byte (bei 16 Bit) sein kann. Wenn beispielsweise der Zuordner versucht hat, einen 8-Byte-Slot zu teilen, um eine 4-Byte-Anforderung zu erfüllen, hätten die verbleibenden 4 Byte nicht genug Platz, um die Zeiger der freien Liste zu codieren.

2 Wenn zum Beispiel die long long auf Ihrer Plattform 8 Byte ist, dann könnte sogar, wenn die internen Datenstrukturen des Zuweisers Blöcke verarbeiten, die kleiner als diese sind, der kleinere Block zugewiesen werden Schieben Sie die nächste 8-Byte-Zuweisung auf eine nicht ausgerichtete Speicheradresse.

    
Branko Dimitrijevic 08.04.2013 15:05
quelle