Warum großer Objekthaufen und warum interessiert es uns?

105

Ich habe über Generationen und großen Objekthaufen gelesen. Aber ich verstehe immer noch nicht, welche Bedeutung (oder welchen Nutzen) es hat, einen großen Objekthaufen zu haben?

Was hätte schief gehen können (in Bezug auf Leistung oder Speicher), wenn sich CLR beim Speichern großer Objekte nur auf Generation 2 verlassen hätte (wenn man bedenkt, dass der Schwellenwert für Gen0 und Gen1 klein ist, um große Objekte zu handhaben)?

Manish Basantani
quelle
6
Dies gibt mir zwei Fragen an .NET-Designer: 1. Warum wird keine LOH-Defragmentierung aufgerufen, bevor eine OutOfMemoryException ausgelöst wird? 2. Warum nicht haben LOH-Objekte eine Affinität zum Zusammenbleiben (große bevorzugen das Ende des Haufens und kleine am Anfang)
Jacob Brewer

Antworten:

195

Eine Garbage Collection entfernt nicht nur nicht referenzierte Objekte, sondern komprimiert auch den Heap. Das ist eine sehr wichtige Optimierung. Dies macht nicht nur die Speichernutzung effizienter (keine nicht verwendeten Lücken), sondern auch den CPU-Cache wesentlich effizienter. Der Cache ist bei modernen Prozessoren eine große Sache, sie sind eine einfache Größenordnung schneller als der Speicherbus.

Die Komprimierung erfolgt einfach durch Kopieren von Bytes. Das braucht jedoch Zeit. Je größer das Objekt ist, desto wahrscheinlicher überwiegen die Kosten für das Kopieren die möglichen Verbesserungen der CPU-Cache-Nutzung.

Deshalb haben sie eine Reihe von Benchmarks durchgeführt, um den Break-Even-Punkt zu bestimmen. Und kam zu 85.000 Bytes als Grenzwert, an dem das Kopieren die Leistung nicht mehr verbessert. Mit einer besonderen Ausnahme für Double-Arrays gelten sie als "groß", wenn das Array mehr als 1000 Elemente enthält. Dies ist eine weitere Optimierung für 32-Bit-Code. Der Heap-Allokator für große Objekte verfügt über die spezielle Eigenschaft, dass er Speicher an Adressen zuweist, die auf 8 ausgerichtet sind, im Gegensatz zum regulären Generations-Allokator, der nur auf 4 ausgerichtet ist. Diese Ausrichtung ist eine große Sache für das Doppelte Das Lesen oder Schreiben eines falsch ausgerichteten Doppels ist sehr teuer. Seltsamerweise erwähnen die spärlichen Microsoft-Informationen niemals lange Arrays, nicht sicher, was damit los ist.

Fwiw, es gibt viele Programmiererangst darüber, dass der große Objekthaufen nicht komprimiert wird. Dies wird immer dann ausgelöst, wenn Programme geschrieben werden, die mehr als die Hälfte des gesamten verfügbaren Adressraums belegen. Verwenden Sie anschließend ein Tool wie einen Speicherprofiler, um herauszufinden, warum das Programm bombardiert wurde, obwohl noch viel nicht verwendeter virtueller Speicher verfügbar war. Ein solches Werkzeug zeigt die Löcher im LOH, unbenutzte Speicherblöcke, in denen zuvor ein großes Objekt lebte, aber Müll gesammelt wurde. Dies ist der unvermeidliche Preis des LOH. Das Loch kann nur durch eine Zuordnung für ein Objekt mit gleicher oder kleinerer Größe wiederverwendet werden. Das eigentliche Problem besteht darin, dass ein Programm jederzeit den gesamten virtuellen Speicher belegen darf .

Ein Problem, das ansonsten vollständig verschwindet, wenn nur der Code auf einem 64-Bit-Betriebssystem ausgeführt wird. Bei einem 64-Bit-Prozess stehen 8 Terabyte Adressraum für den virtuellen Speicher zur Verfügung, 3 Größenordnungen mehr als bei einem 32-Bit-Prozess. Ihnen können einfach keine Löcher ausgehen.

Kurz gesagt, das LOH macht den Code effizienter. Auf Kosten der Nutzung des verfügbaren virtuellen Speicheradressraums weniger effizient.


UPDATE, .NET 4.5.1 unterstützt jetzt das Komprimieren der Eigenschaft LOH, GCSettings.LargeObjectHeapCompactionMode . Beachten Sie bitte die Konsequenzen.

Hans Passant
quelle
3
@ Hans Passant, könnten Sie bitte das x64-System klären, Sie meinen, dieses Problem verschwindet vollständig?
Johnny_D
Einige Implementierungsdetails des LOH sind sinnvoll, andere rätseln mich. Ich kann zum Beispiel verstehen , dass , wenn viele große Objekte erstellt werden und verlassen, es in der Regel wünschenswert sein kann , um sie zu löschen en masse in einer Gen2 Sammlung als Stückwerk in Gen0 Sammlungen, aber wenn man schafft und Abbrüche zB ein Array von 22.000 Strings , auf die Es gibt keine externen Referenzen. Welchen Vorteil hat es, wenn Gen0- und Gen1-Sammlungen alle 22.000 Zeichenfolgen als "live" kennzeichnen, ohne Rücksicht darauf, ob eine Referenz auf das Array vorhanden ist?
Supercat
6
Natürlich ist das Fragmentierungsproblem auf x64 genauso. Es wird nur noch ein paar Tage dauern, bis Ihr Serverprozess ausgeführt wird.
Lothar
1
Hmm, nein, unterschätze niemals 3 Größenordnungen. Wie lange es dauert, einen 4-Terabyte-Heap zu sammeln, können Sie nicht vermeiden, lange bevor er sich dem nähert.
Hans Passant
2
@HansPassant Könnten Sie bitte auf diese Aussage näher eingehen: "Wie lange es dauert, einen 4-Terabyte-Heap zu sammeln, können Sie nicht vermeiden, lange bevor er sich dem nähert."
relativ
9

Wenn die Größe des Objekts größer als ein festgelegter Wert ist (85000 Byte in .NET 1), wird es von CLR in den Heap für große Objekte verschoben. Dies optimiert:

  1. Objektzuordnung (kleine Objekte werden nicht mit großen Objekten gemischt)
  2. Müllabfuhr (LOH nur bei voller GC gesammelt)
  3. Speicherdefragmentierung (LOH wird nie selten komprimiert)
oleksii
quelle
9

Der wesentliche Unterschied zwischen SOH (Small Object Heap) und LOH (Large Object Heap) besteht darin, dass der Speicher in SOH beim Sammeln komprimiert wird, LOH jedoch nicht, wie in diesem Artikel dargestellt. Das Verdichten großer Objekte kostet viel. Ähnlich wie bei den Beispielen im Artikel benötigt das Verschieben eines Bytes im Speicher 2 Zyklen, und das Komprimieren eines 8-MB-Objekts in einem 2-GHz-Computer benötigt 8 ms, was hohe Kosten verursacht. Angesichts der Tatsache, dass große Objekte (in den meisten Fällen Arrays) in der Praxis weit verbreitet sind, ist dies vermutlich der Grund, warum Microsoft große Objekte in den Speicher steckt und LOH vorschlägt.

Übrigens, laut diesem Beitrag erzeugt LOH normalerweise keine Speicherfragmentprobleme.

Grapeot
quelle
1
Das Laden großer Datenmengen in verwaltete Objekte stellt normalerweise die 8-ms-Kosten für die Komprimierung des LOH in den Schatten. In der Praxis sind bei den meisten Big-Data-Anwendungen die LOH-Kosten neben dem Rest der Anwendungsleistung trivial.
Shiv
3

Das Prinzip ist, dass es unwahrscheinlich (und möglicherweise ein schlechtes Design) ist, dass ein Prozess viele kurzlebige große Objekte erstellt, sodass die CLR große Objekte einem separaten Heap zuordnet, auf dem GC nach einem anderen Zeitplan als dem regulären Heap ausgeführt wird. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

Myles McDonnell
quelle
Wenn Sie beispielsweise große Objekte auf Generation 2 setzen, kann dies die Leistung beeinträchtigen, da das Komprimieren des Speichers sehr lange dauern würde, insbesondere wenn eine kleine Menge freigegeben wurde und RIESIGE Objekte an einen neuen Speicherort kopiert werden mussten. Das aktuelle LOH wird aus Leistungsgründen nicht verdichtet.
Christopher Currens
Ich denke, es ist nur schlechtes Design, weil der GC nicht gut damit umgeht.
CodesInChaos
@ CodeInChaos Anscheinend gibt es einige Verbesserungen in .NET 4.5
Christian.K
1
@CodeInChaos: Während es für das System möglicherweise sinnvoll ist, bis zu einer Gen2-Sammlung zu warten, bevor versucht wird, Speicher von selbst kurzlebigen LOH-Objekten zurückzugewinnen, sehe ich keinen Leistungsvorteil beim Deklarieren von LOH-Objekten (und von Objekten, an denen sie beteiligt sind) Referenzen) leben bedingungslos während der Sammlungen gen0 und gen1. Gibt es einige Optimierungen, die durch eine solche Annahme ermöglicht werden?
Supercat
@supercat Ich habe mir den von Myles McDonnell erwähnten Link angesehen. Mein Verständnis ist: 1. Die LOH-Sammlung erfolgt in einem Gen 2 GC. 2. Die LOH-Sammlung enthält keine Verdichtung (zum Zeitpunkt der Artikelerstellung). Stattdessen werden tote Objekte als wiederverwendbar markiert und diese Löcher dienen zukünftigen LOH-Zuweisungen, wenn sie groß genug sind. Aufgrund von Punkt 1 ist es in diesem Fall besser, LOH so weit wie möglich zu vermeiden, wenn man bedenkt, dass ein Gen 2 GC langsam wäre, wenn es viele Objekte in Gen 2 gibt.
Robbie Fan
0

Ich bin kein Experte für die CLR, aber ich würde mir vorstellen, dass ein dedizierter Heap für große Objekte unnötige GC-Sweeps der vorhandenen Generationsheaps verhindern kann. Das Zuweisen eines großen Objekts erfordert eine erhebliche Menge an zusammenhängendem freiem Speicher. Um dies aus den verstreuten "Löchern" in den Generationshaufen zu erhalten, benötigen Sie häufige Komprimierungen (die nur mit GC-Zyklen durchgeführt werden).

Chris Shain
quelle