PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : .NET Speicherprobleme


PatkIllA
2012-03-23, 17:59:41
Wir haben ein schon umfangreicheres Programm und haben nach mehr oder weniger langer Zeit immer mal wieder seltsame Probleme. Das Programm selbst ist eine WPF Anwendung mit Tabs.
Ein großer Teil ist das Zeichnen von Karten per GDI. Dabei kommt es immer mal wieder vor, dass gerade beim Bilder laden oder erstellen oder Zeichnen die diversesten Fehler auftreten.
Die Karte selbst wird dann per WriteableBitmap und InteropBitmap angezeigt. Die Speicherauslastung liegt dabei die meiste Zeit im unteren dreistelligen MB-Bereich.

Mir ist im Process Explorer aufgefallen, dass die VirtualSize anscheinend immer weiter wächst und kurz vor 2 GB kann man definitiv kurze Zeit später mit einem Absturz rechnen.

Wenn ich im CLR Profiler einen Speichersnapshot anschaue, dann sehe ich so auch nicht, dass Objekte am Leben bleiben.

Nach was kann man noch suchen und welche Tools gibt es noch?

Demirug
2012-03-23, 19:41:46
WinDBG mit SOS Extensions. Das ultimative Tool für Speicheranalysen. Ist aber Lowlevel, d.h. alles nur mit Konsolen Kommandos

Elemental
2012-03-23, 22:40:20
http://memprofiler.com/
http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/

Monger
2012-03-24, 00:16:21
Es gibt ne Hand voll spezieller Profiler für WPF, die ich allerdings selbst nicht getestet habe, wie z.B. :

WPF Performance Suite (http://msdn.microsoft.com/en-us/library/aa969767.aspx)

Schau dir mal auch im Task Manager an, wie sich die GDI Objekte / Handles entwickeln. Da gibt es gerne mal Leaks.

Der Garbage Collector unterscheidet zwischen kleinen und großen Objekten. Die größeren disposed er normalerweise nur beim Applikationsende, weil die Speicherfragmentierung sonst sehr viel CPU Leistung fressen würde. Gibt wenige Objekte die normalerweise so groß sind, aber Bitmaps können dazu gehören. Vielleicht erklärt das deinen rapide wachsenden virtuellen Speicher.

Elemental
2012-03-24, 08:39:46
Probleme kanns geben, wenn der Garbage Collector denkt, dass es kleine Objekte sind, aber es eigentlich grosse sind.
So z.B. wenn man COM mit COM-Interop verwendet. D.h. es gibt ein kleines COM-Interop Objekt aber dahinter verbirgt sich ein grosses COM-Objekt.

Coda
2012-03-24, 11:21:25
Was sagt denn VMMap?
http://technet.microsoft.com/en-us/sysinternals/dd535533

PatkIllA
2012-03-26, 13:26:30
Es gibt ne Hand voll spezieller Profiler für WPF, die ich allerdings selbst nicht getestet habe, wie z.B. :
WPF Performance Suite (http://msdn.microsoft.com/en-us/library/aa969767.aspx)Die kenne ich. Wüsste aber jetzt nicht wie mir das weiterhilft.
Schau dir mal auch im Task Manager an, wie sich die GDI Objekte / Handles entwickeln. Da gibt es gerne mal Leaks.Die bleibt bei 86 stehen, während der Speicher weiter hochgeht.
Der Garbage Collector unterscheidet zwischen kleinen und großen Objekten. Die größeren disposed er normalerweise nur beim Applikationsende, weil die Speicherfragmentierung sonst sehr viel CPU Leistung fressen würde. Gibt wenige Objekte die normalerweise so groß sind, aber Bitmaps können dazu gehören. Vielleicht erklärt das deinen rapide wachsenden virtuellen Speicher.Disposen sollte er ja schon alles. Wenn man ständig große Objekte erzeugt und freigibt müsste das ja Lücken im Addressraum geben. Kann das ein Problem sein.
Probleme kanns geben, wenn der Garbage Collector denkt, dass es kleine Objekte sind, aber es eigentlich grosse sind.
So z.B. wenn man COM mit COM-Interop verwendet. D.h. es gibt ein kleines COM-Interop Objekt aber dahinter verbirgt sich ein grosses COM-Objekt.Der ganze GDI Krams ist ja in System.Drawing gekapselt.
Wir setzen zwar auch COM und Pinvke ein, aber das Problem tritt auch im einfachsten Fall ohne diese Techniken auf.
Was sagt denn VMMap?
http://technet.microsoft.com/en-us/sysinternals/dd535533
Hier mal ein VMMap + Process Explorer Screenshot.

Monger
2012-03-26, 14:52:11
Disposen sollte er ja schon alles. Wenn man ständig große Objekte erzeugt und freigibt müsste das ja Lücken im Addressraum geben. Kann das ein Problem sein.
Der Garbage Collector räumt große Objekte nicht weg. Er markiert sie zwar als disposed, aber der Speicher wird nicht erneut belegt. Das würde erklären warum scheinbar dein Prozess eigentlich wenig Speicher verbraucht, aber vom virtuellen Speicher große Mengen belegt sind.

Demirug
2012-03-26, 15:37:45
Der Garbage Collector räumt große Objekte nicht weg. Er markiert sie zwar als disposed, aber der Speicher wird nicht erneut belegt. Das würde erklären warum scheinbar dein Prozess eigentlich wenig Speicher verbraucht, aber vom virtuellen Speicher große Mengen belegt sind.

Das ist so nicht ganz richtig. Der Speicher wird von anderen großen Objekten wieder belegt. Aber er wird nicht zusammengeschoben und fragmentiert deswegen sehr leicht

PatkIllA
2012-03-26, 16:25:23
Bei dem VM Map Screenshot von oben konnte ich auch schon sehr schön sehen, dass da unter den 700 MB "Free" ein großer Block mit 136 MB war und über 100 Bereiche im kleinen einstelligen MB Bereich in die ein Bild in FullHD in ARGB schon nicht mehr reinpassen würde. Das Programm war jetzt in dem Beispiel oben auch noch nicht so weit, dass es gleich abstürzen würde.
Dafür war das jetzt auch nur die Minimalvariante. Wo noch kein Unmanaged Code oder externe COM-Komponenten gelaufen sind.

Sollte dieser Free Bereich denn auch wieder kleiner werden? Kann man den GC anweisen doch bitte mal die managed Objekte umzuverschieben?

Monger
2012-03-26, 19:47:32
Die Grundidee bei .NET ist es, dass große Ressourcen die für den Ablauf eines Programms notwendig sind (Sounds, Grafiken, sonstige Ressourcen) bei Bedarf einmalig geladen werden, und danach nie wieder. Genau so arbeitet auch der Resourcenmanager.

Objekte dagegen die zur Laufzeit entstehen und wieder verschwinden, sollten möglichst klein sein damit der Garbage Collector sie einzeln schön fix entsorgen kann.

Deshalb: überleg mal, ob du algorithmisch irgendwas drehen kannst. Ich kenn das Programm nicht, aber falls nicht bereits geschehen: nicht ständig die Zeichenfläche gegen ein anderes Bitmap austauschen, sondern das Bitmap recyclen und neu übermalen. Schauen, ob man schwergewichtige Controls nicht durch leichtgewichtigere Varianten ersetzen kann. Mehr mit Streams arbeiten statt zwischenspeichern.

Und das wichtigste: erst messen, dann ausbessern, dann erneut messen und schauen ob sich wirklich was zum besseren gewendet hat.

PatkIllA
2012-03-26, 19:56:39
Die Bitmapdaten auf Platte sind unter Umständen zweistellige Gigabyte groß und auch wenn der Benutzer wahrscheinlich nur einen Teil davon sieht sind da etliche hundert MB, die im Programmverlauf angesehen werden.

Sparen könnte ich noch bei den Zeichenbitmaps. Die werden z.B. beim Resizen immer neu erstellt.
Bei den Interopbitmaps könnte da gleich einen größeren Bereich anfordern und nur das Bitmap, dass sich da den Speicherbereich teilt anpassen. So häufig kommt das aber auch nicht vor. Ansonsten gibt es da drei Bitmaps pro Karte (und im Schnitt im Tab)
Und da zwischen Tabs noch was zu behalten möchte ich mir nicht antun. So oft werden die Benutzer die Tabs auch nicht auf und zu machen.
Was meinst du denn mit leichtgewichtigere Controls?
Und wie sollen mir Streams helfen.
WPF selbst cached ja auch noch mal großzütig und das oft mit größeren Bildern.
Die GDI Zeichenfunktionen selbst sind schon etwas älter und bei der alten Forms Anwendung hatten wir das Problem praktisch nicht. Aber da hatten wir auch noch keine Tabs.

In .NET 4.5 sind wohl noch ein paar Optimierungen für das Wiederbenutzen von freien Speicherbereichen.
http://blogs.msdn.com/b/dotnet/archive/2011/10/03/large-object-heap-improvements-in-net-4-5.aspx

Wobei sich der LOH doch nur auf die Managed Objekte bezieht. Was gibt es denn da praktisch außer großen Arrays oder sehr langen String. Und halt die vielen Datenstrukturen, die intern wieder auf Arrays beruhen.
Die ganzen Bild wandern doch sowie in unmanaged Code bereiche oder?

Monger
2012-03-26, 20:26:47
Was meinst du denn mit leichtgewichtigere Controls?

Konkret: wir haben auf Arbeit ein Tool, dessen DataGrid etliche tausend Zeilen enthalten kann, und jede Zeile ist ein ziemlich komplexes Control, zusammengesetzt aus Checkboxen, Textboxen, Labels und Linien. Jedesmal wenn du gescrollt hast, wurden neue Controls instanziiert, und die Bedienung fühlte sich zäh wie Kaugummi an. Einer der ersten, ziemlich drastischen Verbesserungen war: wir haben alle Label Controls durch TextBlock Controls ersetzt. Die beiden Controls sehen eigentlich ziemlich ähnlich aus, und haben oberflächlich betrachtet auch die selbe Aufgabe (Text anzeigen), aber Label leitet sich von Control ab, und besitzt das gesamte Event Handling. TextBlock rendert einfach nur. Das Binding blieb immer noch das selbe, aber die CPU Last war auf einen Schlag VIEL niedriger.

Ich glaube, das Beispiel ist jetzt auf deinen Fall nicht übertragbar, aber ich denke du verstehst worauf ich hinaus will.


Wobei sich der LOH doch nur auf die Managed Objekte bezieht. Was gibt es denn da praktisch außer großen Arrays oder sehr langen String.
Die ganzen Bild wandern doch sowie in unmanaged Code bereiche oder?
Ein Bitmap ist meines Wissens vollständig Managed. Bitmap ist ja erstmal nur ein Datencontainer, also quasi ein großes Array von Farben. Mitm Rendern hat das erstmal noch gar nichts zu tun.

Exxtreme
2012-03-26, 20:30:17
Hmmm, kann man sich bei .NET aussuchen mit welcher VM das Ding läuft? Weil die Problematik sieht mir ziemlich nach Fragmentierter-32-Bit-Adressraum aus. Vielleicht irgendwie eine 64-Bit-VM erzwingen, natürlich falls man auch ein 64-Bit-Windows nutzt.

PatkIllA
2012-03-26, 20:32:15
Ein Bitmap ist meines Wissens vollständig Managed. Bitmap ist ja erstmal nur ein Datencontainer, also quasi ein großes Array von Farben. Mitm Rendern hat das erstmal noch gar nichts zu tun.Also wenn ich mit ILSpy oder Reflector in den Code gucke ist bei System.Drawing praktisch alles nur ein Wrapper für Platform Invoke.
Bei WPF ist das ganze Zeichnen auch Native Code im Media Integration Layer, was intensiv Gebrauch von Direct3D und Windows Imaging Components macht.
Hmmm, kann man sich bei .NET aussuchen mit welcher VM das Ding läuft? Weil die Problematik sieht mir ziemlich nach Fragmentierter-32-Bit-Adressraum aus. Vielleicht irgendwie eine 64-Bit-VM erzwingen, natürlich falls man auch ein 64-Bit-Windows nutzt.Ein großer Teil der Kunden hat aber noch 32 Bit. Und dann haben wir noch ein paar Komponenten, die es nicht in 64 Bit gibt oder wo wir erst eine neue Version kaufen müssten.

Exxtreme
2012-03-26, 20:48:14
Ein großer Teil der Kunden hat aber noch 32 Bit. Und dann haben wir noch ein paar Komponenten, die es nicht in 64 Bit gibt oder wo wir erst eine neue Version kaufen müssten.
Hmmm, dann kann ich nur den Tipp geben: mit Speicher streng haushalten, zumindest in der 32-Bit-Version der Software. Ihr seid halt nicht die ersten, die in diese Problematik reinlaufen. Sobald man anfängt die 2 GB ordentlich zu belegen und dann auch noch recht große Brocken allokiert dann ist eine gewisse Wahrscheinlichkeit da, dass plötzlich nicht mehr genug RAM am Stück verfügbar ist.

Sauber umgehen lässt sich das mit 64-Bit. Vielleicht auch mal testweise den 3GB-Switch (http://support.microsoft.com/kb/316739/EN-US/) in der boot.ini versuchen. Wenn das Problem entschärft wird dann liegt es am Adressraum.

PatkIllA
2012-03-26, 20:53:01
Hmmm, dann kann ich nur den Tipp geben: mit Speicher streng haushalten, zumindest in der 32-Bit-Version der Software. Ihr seid halt nicht die ersten, die in diese Problematik reinlaufen. Sobald man anfängt die 2 GB ordentlich zu belegen und dann auch noch recht große Brocken allokiert dann ist eine gewisse Wahrscheinlichkeit da, dass plötzlich nicht mehr genug RAM am Stück verfügbar ist. Das haushalten ist ja so eine Sache. Die Grafikdaten müssen halt am Stück vorliegen. Da haben wir wahrscheinlich noch Glück. Denn zumindest die geladenen Bitmaps von Platten haben nur wenige verschiedene Größen und Formate. Da müsste also ein freigegebenes genau den Platz für ein neues freimachen. Nur doof wenn in den Bereich zwischendurch noch ein kleines gesetzt wird.
Ich hatte bis vorher gedacht, dass 10 MB Bereiche noch nicht wirklich ein Problem sind.
Sauber umgehen lässt sich das mit 64-Bit. Vielleicht auch mal testweise den 3GB-Switch (http://support.microsoft.com/kb/316739/EN-US/) in der boot.ini versuchen. Wenn das Problem entschärft wird dann liegt es am Adressraum.Da müsste dann aber auch noch das LAA Flag gesetzt sein, was bei .NET Anwendungen nicht der Fall ist.

Exxtreme
2012-03-26, 20:56:55
Da müsste dann aber auch noch das LAA Flag gesetzt sein, was bei .NET Anwendungen nicht der Fall ist.

Mit "Editbin /LARGEADDRESSAWARE" soll es angeblich gehen, dass auch .NET-Programme mehr als 2 GB nutzen können. Zum Testen würde ich es machen. ;)

myMind
2012-03-26, 21:50:18
Wenn ich GDI höre, dann kann ich mich nicht dagegen wehren, als erstes an Handles zu denken. Wie sieht es mit denen aus? Bewegen sich die Handles im grünen Bereich (GDI Handles, Handle Count)?

Mit Administrative Tools > Performance kann man schon allerhand machen. Insbesondere der Counter ".NET CLR Memory" gibt einiges her. Wichtig: Schauen, ob es in einem bestimmten Heap-Bereich Leaks gibt (Gen 0, Gen 1, Gen 2, Large Object). Insbesondere letzterer hat sich schon ziemlich unbeliebt gemacht.

Ich würde jedenfalls als erstes darauf hinarbeiten, herauszufinden, ob es ein Problem im managed oder im unmanaged Bereich gibt, was Du im Prinzip mit dem Windows "Performance" Tool schon herausfinden kannst.

Den von Elemental empfohlenen Scitech .NET Memory Profiler kann ich dir ebenfalls wärmstens ans Herz legen. Mit dem Tool mehrere Snapshots in einer möglichst aufwandsneutralen Dauerbelastungsmessung machen und danach die Snapshots vergleichen. Schauen, ob sich die Anzahl einer Objektklasse unerwarteter Weise stetig nach oben bewegt. Wenn ja dann könnte es das schon sein. Das wäre dann Glück, denn Dein Problem liegt auf der gemanageten Seite. Wenn keine Anstiege zu finden sind, dann hast Du ein echtes Problem.

Im unmanaged Bereich scharf hinschauen, ob immer die entsprechenden Aufräumfunktionen verwendet wurden, wenn disposed wird.

Wenn man gar nicht mehr weiterkommt, dann macht es sich an dieser Stelle erfahrungsgemäß bezahlt, wenn
a) die Anwendung gut in Komponenten zerlegbar ist und
b) man ein Testframework im Rücken hat.
Die Aufgabe: Baue einen (Dauer-)Test, der dir den Fehler erzeugt. Wenn der gefunden ist, Komponenten oder Teile ausbauen, bis der Fehler nicht mehr auftaucht. Dann enthält das gerade ausgebaute Teil den Fehler. Das ist sehr Zeitintensiv, aber was soll man machen.


Vielleicht ist dies ja auch der richtig Zeitpunkt, um sich von GDI zu trennen und mit WPF die Bitmaps zu malen.:wink:

PatkIllA
2012-03-26, 22:02:44
Wir benutzen nicht direkt GDI sondern die Wrapper in System.Drawing
Das wird ja notfalls per Finalizer weggeräumt, es wird da aber sehr gewissenhaft auch direkt disposed. Gui testen ist ja leider auch so eine Sache.
Außerdem klingt das auch ein bisschen so, als ob das einzeln noch ganz gut, sich in die großen Speicherbereiche aber immer mal wieder ein kleineres reindrückt.

Mit WPF zu maleln dürfte wohl eher nicht gehen. Das sind dann öfters Geometrien in Größenordnungen von hundert tausenden. Da wäre eher Direct2D angesagt.

Und umstellen wird auch nicht wirklich gehen, da das Zeichnen doch so eine der größeren Komponenten ist und das Neuschreiben wohl doch einige Mannmonate in Anspruch nehmen würde. Außerdem wird das auch in einigen Tools außerhalb von GUIs eingesetzt.

][immy
2012-03-26, 23:48:11
mal so ne frage, kann ich .net irgendwie dazu bringen den speicher zu defragmentieren?
auch wenn das prozessoraufwendig wäre, es gäbe ja vielleicht mal "ruhezeiten" bei denen man das gut einschieben könnte.

ich weiß zwar wie ich ein programm aus dem hauptspeicher auf die platte schiebe (zumindest inaktive teile) aber natürlich existiert das problem mit dem speicher dann ja auf der platte weiter (wie hier schon geschrieben wurde).

was mir ansonsten noch einfallen würde bei dem thema, einfach mal (auch wenn es recht viel rechenzeit benötigt) wie viel speicher gerade genutzt wird. dann könntest du auf jeden fall schon mal feststellen, an welchen stellen besonders viel speicher angefordert wurde.
zumindest wüsstest du dann welchen teil du besonders beachten musst.

ein ähnliches speicherproblem kenne ich bisher z.b. von silverlight. sobald man mit bitmap objekten arbeiten sollte man dieses möglichst wiederverwenden, da wenn man ja z.b. ein jpg öffnet plötzlich ein dekomprimiertes bitmap im speicher liegen hat, wodurch bei relativ großen bildern (z.B. gescannte pläne) schnell mal der speicher volläuft.

PatkIllA
2012-03-26, 23:57:13
[immy;9226873']ich weiß zwar wie ich ein programm aus dem hauptspeicher auf die platte schiebeWie willst du das denn machen außer den restlichen Speicher sinnlos vollmüllen?
[immy;9226873']was mir ansonsten noch einfallen würde bei dem thema, einfach mal (auch wenn es recht viel rechenzeit benötigt) wie viel speicher gerade genutzt wird. dann könntest du auf jeden fall schon mal feststellen, an welchen stellen besonders viel speicher angefordert wurde.
Welche Bereiche große Speicherblöcke brauchen ist mir schon klar.
[immy;9226873']ein ähnliches speicherproblem kenne ich bisher z.b. von silverlight. sobald man mit bitmap objekten arbeiten sollte man dieses möglichst wiederverwenden, da wenn man ja z.b. ein jpg öffnet plötzlich ein dekomprimiertes bitmap im speicher liegen hat, wodurch bei relativ großen bildern (z.B. gescannte pläne) schnell mal der speicher volläuft.WPF cached einmal geladene Bilder sowieso. Und einmal geladene Bitmaps kann man in WPF praktisch nicht manipulieren.

][immy
2012-03-27, 00:14:05
Wie willst du das denn machen außer den restlichen Speicher sinnlos vollmüllen?
es gibt nen befehl den man aufrufen muss ... kann es grad von hier nicht nachschauen, ist aber ein ähnlicher effekt als wenn du das fenster minimierst, auch dann wird der hauptspeicher häufig automatisch entleert und aktuell nicht benötigte teile auf die platte verschoben.

wenn ich dran denke schaue ich morgen mal nach, wird dir natürlich nur nicht bei deinem problem helfen. macht die applikationen im endeffekt aber ein wenig langsamer, kommt beim kunden aber ab und an mal besser an, wenn das programm scheinbar im taskmanager weniger hauptspeicher belegt ;)


achja, müsste nicht auch ein möglicher ansatz sein, deine bildbearbeitung in eine eigene app-domain zu schieben. die verhält sich doch eigentlich wie ein abgekapselter prozess. bin mir grad nur nicht sicher ob man darüber die 2gb (bzw. meiner erfahrung nach sind es meist nur 1,3 GB in 32 Bit) knacken kann. wenn man diese allerdings abräumt, müsste der speicher aber wieder komplett freigegeben werden.
ist vielleicht ein wenig komplizierter, müsste aber dafür sorgen, das du .net dazu zwingen kannst einige sachen halt nicht zu cachen und den speicher wieder freizugeben.

PatkIllA
2012-03-27, 00:33:35
[immy;9226909']es gibt nen befehl den man aufrufen muss ... kann es grad von hier nicht nachschauen, ist aber ein ähnlicher effekt als wenn du das fenster minimierst, auch dann wird der hauptspeicher häufig automatisch entleert und aktuell nicht benötigte teile auf die platte verschoben.
Den meinst du aber jetzt nicht?
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686234%28v=vs.85%29.aspx
[immy;9226909']wenn ich dran denke schaue ich morgen mal nach, wird dir natürlich nur nicht bei deinem problem helfen. macht die applikationen im endeffekt aber ein wenig langsamer, kommt beim kunden aber ab und an mal besser an, wenn das programm scheinbar im taskmanager weniger hauptspeicher belegt ;)
Mag schon sein. Der Taskmanager vereinfacht das Problem aber sehr stark und benutzt teilweise noch falsche Begriffe.
[immy;9226909']achja, müsste nicht auch ein möglicher ansatz sein, deine bildbearbeitung in eine eigene app-domain zu schieben. die verhält sich doch eigentlich wie ein abgekapselter prozess. bin mir grad nur nicht sicher ob man darüber die 2gb (bzw. meiner erfahrung nach sind es meist nur 1,3 GB in 32 Bit) knacken kann. wenn man diese allerdings abräumt, müsste der speicher aber wieder komplett freigegeben werden.
ist vielleicht ein wenig komplizierter, müsste aber dafür sorgen, das du .net dazu zwingen kannst einige sachen halt nicht zu cachen und den speicher wieder freizugeben.AFAIK laufen mehrere AppDomains in einem Prozess. Man könnte natürlich Teile out of Prozess auslagern, aber das wird dann doch etwas heftig.