2013-03-20 9 views
9

Aufgrund scheinbar voreiliger Out of Memory-Ausnahmen haben wir die Speicherbelegung verschiedener .NET-Konstrukte genau untersucht ... besonders große Objekte, die dazu neigen Fragmentieren Sie den Large Object Heap, was zu vorzeitigen Out of Memory-Ausnahmen führt. Ein Bereich, der ein wenig überraschend ist, sind die .NET Image-Klassen: Bitmap und Metafile.Die Speicherauslastung und Fragmentierung von .NET-Bildklassen: Bitmap vs. Metafile

Hier ist, was wir denken, wir haben gelernt, aber nicht gelungen, MS-Dokumentation zu finden, um zu überprüfen, so würden wir keine Bestätigung andere schätzen geben kann:

(1) Wenn Sie ein Bitmap-Objekt aus einem komprimierten Raster erstellen Datei (JPG, PNG, GIF, etc), verbraucht es Speicher für ein vollständig unkomprimiertes Pixel-Array mit der vollen Auflösung dieser Datei. So würde zum Beispiel ein 5MB JPG, das 9000x3000 Pixel groß ist, in 9000x3000x3 Bytes (angenommen 24 Bit Farbe, kein Alpha) oder 81MB Speicherbedarf erweitert. Richtig?

(1a) Es gibt einige Beweise (siehe 2b unten), dass es auch das ursprüngliche komprimierte Format speichert ... also eigentlich 86MB in diesem Fall. Aber das ist unklar ... weiß jemand?

(2) Wenn Sie ein Metafile-Objekt erstellen und dann eine Raster-Datei (JPG, PNG, GIF usw.) darauf zeichnen, wird nur Speicher für die komprimierte Datei benötigt. Wenn Sie also ein 5MB JPG mit 9000x3000 Pixeln in eine Metafile zeichnen, verbraucht es nur ungefähr 5MB Speicher. Richtig?

(2a) Um eine Rasterdatei in ein Metafile-Objekt zu zeichnen, scheint der einzige Weg darin zu bestehen, ein Bitmap mit der Datei zu laden und dann die Bitmap in die Metafile zu zeichnen. Gibt es einen besseren Weg, der nicht das zeitweilige Laden dieser riesigen Bitmap-Daten beinhaltet (und die damit verbundene Speicherfragmentierung verursacht)?

(2b) Wenn Sie eine Bitmap in eine Metafile zeichnen, wird ein komprimiertes Format verwendet, dessen Größe der ursprünglichen komprimierten Datei ähnelt. Wird die ursprüngliche komprimierte Datei in der Bitmap gespeichert? Oder macht es das, indem es die erweiterte Bitmap mit den ursprünglichen Komprimierungseinstellungen komprimiert?

(3) Wir gingen ursprünglich davon aus, dass große (> 85 KB) Image-Objekte im Large Object Heap platziert werden. In der Tat scheint das nicht der Fall zu sein. Vielmehr ist jedes Bitmap und jede Metafile ein 24-Byte-Objekt im Small Object Heap, das sich auf einen Block Native Memory bezieht, der die echten Daten enthält. Richtig?

(3a) Wir gehen davon aus, dass Native Memory wie Large Object Heap ist, da sie nicht kompaktiert werden kann ... sobald das große Objekt in Native Memory gelegt wird, wird es niemals verschoben und kann daher die Fragmentierung des Native Memory verursachen so viele Probleme wie die Fragmentierung von Large Object Heap. Wahr? Oder gibt es eine spezielle Behandlung der zugrunde liegenden Bitmap/Metafile-Daten, die effizienter ist?

(3b) Also, es scheint vier unabhängige Speicherblöcke zu geben, die separat verwaltet werden, und jedes davon kann zu den gleichen Out-of-Memory-Ausnahmen führen: Small Object Heap (gemanagte Objekte < 85KB, kompaktiert durch GC), Large Object Heap (verwaltete Objekte> 85 KB, die von GC gesammelt, aber nicht komprimiert werden), Native Memory (nicht verwaltete Objekte, vermutlich nicht komprimiert) und Desktop Heap (wobei Windows-Handles und solche begrenzten Ressourcen verwaltet werden). Habe ich diese vier richtig dokumentiert? Gibt es andere, denen wir uns bewusst sein sollten?

Jede Klarheit, die jemand auf dem oben genannten bereitstellen kann, würde sehr geschätzt werden. Wenn es ein gutes Buch oder einen Artikel gibt, der das oben genannte vollständig erklärt, lass es mich wissen. (Ich bin glücklich, die erforderliche Lektüre zu machen; aber die überwiegende Mehrheit der Bücher nicht so tief, und damit sagen Sie mir nichts, was ich nicht bereits weiß.)

Vielen Dank!

+1

GDI + speichert Bilddaten im nicht verwalteten Speicher. Also keine Ihrer Annahmen sind nah. Das Vergessen von Bitmaps ist eine schnelle Möglichkeit, OOM zu treffen. Die Adressraumfragmentierung ist eine andere, ein 32-Bit-Prozess erreicht nach einer Weile 90 MB als Gefahrenzone. –

+0

"keine Ihrer Annahmen sind nahe" ?? Aber alles, was du darüber sagst, stimmt mit meinen obigen Annahmen überein ... also, nicht sicher, was du mir erzählst. Wollen Sie sagen, dass "Native Memory" anders ist als "Unmanaged Memory"? Oder meintest du "alle deine Annahmen sind nah"? –

+0

Auf 2 (a), das Beste, was ich bisher gefunden habe, ist "Nein, aber ja, sorta ...": Erstellen Sie eine separate .exe, die einfach eine beliebige Rasterdatei in eine Bitmap einliest und dann eine neue Metafile öffnet Datei auf der Festplatte und führt DrawImage dieser Bitmap in diese Metafile und beendet.Wenn ich dann in meiner Hauptanwendung auf eine Rasterdatei zeige, anstatt sie zu öffnen und eine Speicherfragmentierung in meiner Hauptanwendung zu erzeugen, rufe ich diese .exe auf, um eine temporäre Datei zu erstellen, die eine Metafile enthält, und dann in meiner Hauptanwendung lese diese Datei direkt in eine Metafile ein. Clunky, aber keine Auswirkungen auf die Hauptanwendung. Irgendwelche besseren Ideen? –

Antwort

2

Ich weiß, dass die Antworten auf einige dieser:

(1) Ja, das ist die Definition eines Bitmap-Bildes ist.

(3) Ja, deshalb implementiert Bitmap die IDisposable-Schnittstelle.

(3a) Das scheint überraschend. Führen Sie die Dispose() -Methode für Ihre Bitmap-Objekte aus, wenn Sie damit fertig sind?

(3b) Mindestens diese vier, ja.

+0

Dies ist definitiv die erste Sache zu überprüfen, zum Beispiel [this] (http://stackoverflow.com/questions/8830770/why-is-there-a-memory-leak-when-i-do-not-dispose -Bitmaps-das-ich-speichern-zu-a) Post – bigge

+0

Ja, unsere Image-Objekte werden ordnungsgemäß entsorgt. Wir führen Speicher-Profiler aus ... es gibt keine Lecks. –

3

Es gibt zwei Möglichkeiten, Bilddaten zu speichern: als Pixel oder als Vektoren. Bitmap ist über Pixel, Metafile ist über Pixel und Vektoren. Vektordaten sind viel effizienter zu speichern.

Um die Bearbeitung von Bitmaps zu ermöglichen, müssen ihre Daten unkomprimiert im Speicher abgelegt werden. Andernfalls müssten GetPixel und SetPixel die Bitmap für jede Änderung dekomprimieren, ändern, neu komprimieren (falls das überhaupt möglich wäre).

Metafiles wurden von Microsoft erstellt und sollten mit GDI arbeiten, daher könnte es einige weitere speichereffiziente Komprimierungsalgorithmen enthalten, die direkt mit der Grafikkarte arbeiten. Außerdem gibt es keine GetPixelSetPixel Methoden für Metadateien, so dass sie nicht im Arbeitsspeicher dekomprimiert werden müssen, um eine Manipulation zu ermöglichen.


Sie sollten sich nicht um die Speicherpools kümmern, die die Laufzeit verwendet. Es gibt viel mehr, und die Laufzeit entscheidet, wo sie Objekte platziert. Außerdem sollten Sie keine Out-of-memory-Ausnahmen beachten, die möglicherweise durch die Verwendung von (großen) Objekten entstehen. Die Runtime wird alles tun, was möglich ist (Objekte in Lücken zwischen anderen Objekten setzen, Heaps komprimieren, verfügbaren virtuellen Speicher erweitern), um sicherzustellen, dass Sie keine Ausnahme wegen zu wenig Arbeitsspeicher erhalten. Wenn Sie eine solche Ausnahme irgendwie erhalten, gibt es wahrscheinlich ein anderes Problem in Ihrem Code, das behoben werden sollte (z. B. ein Speicherverlust).

Eine Übersicht über die Speicherhaufen, Karten und Tabellen: (source)

Heaps, maps and tables used in .NET


Auch Ihre Annahme, dass Objekte von über 85 KiB auf dem Large Object platziert ist Heap nicht ganz richtig. Ist korrekt für am meisten Objekte in der aktuellen Version der CLR, aber zum Beispiel eine 8 KiB-Array von Doppel (1000 Doppel) ist auch auf dem Large Object Heap zugeordnet. Lassen Sie sich einfach die Runtime damit beschäftigen.

+0

Leider "nur die Laufzeit sich damit beschäftigen" funktioniert nur für kleine Objekt Heap. Wir verwenden Speicher-Profiler ... wir haben keine Lecks ... aber wir können 2 GB Speicher nicht mehr verwalten, indem wir ein einzelnes Dokument verwalten, das nicht mehr als 10 MB Daten haben sollte. Wie? Zersplitterung. Redesign, um die Fragmentierung zu minimieren, hat uns bis zu 50MB ... und dann bis zu 100MB gebracht ... und wir setzen diese Bemühungen fort. Daher müssen wir uns aufgrund der Fragmentierung leider darum kümmern. (Danke für den Quelllink, wo du das Bild gemacht hast ... sehr gute Infos.) –

+0

@BrianKennedy Ich habe keine Ahnung was du machst, aber du hast einige Möglichkeiten. Befreie deine Bilder, sobald du damit fertig bist (leere alle Zeiger, bitte den GC, Speicher zu sammeln). Sie können C++/CLI verwenden, um den Speicher für Ihre Bilder selbst zu verwalten. Sie können nach Methoden suchen, die nur den Teil des Bildes laden, den Sie bearbeiten müssen, oder nach Methoden, die das Bild in ihrem komprimierten Format laden und tun, was Sie wollen (anzeigen oder so). – Virtlink

+0

Ja, Virtlink, es scheint, ich kann Metafile anstelle von Bitmap verwenden, um die unkomprimierten Bilder zu vermeiden. Unglücklicherweise scheint es die einzige Möglichkeit, ein JPG in eine Metafile zu bekommen, durch ein Bitmap ... obwohl ich es sofort entsorgen kann, um diesen Speicher freizugeben, wird es immer noch zu einer Fragmentierung des Speichers führen (so scheint es). Daher meine Fragen oben. –