Laden...

Mein Arbeitsspeicherverbrauch explodiert...

Letzter Beitrag vor 19 Jahren 21 Posts 5.215 Views
Mein Arbeitsspeicherverbrauch explodiert...

Hallo!

Ich hab ein kleines Programm gebastelt, dass in bestimmten Abständen Bilder von Webcams downloaded und speichert. Die gespeicherten Bilder kann ich dann animiert anzeigen lassen.

Wenn ich dann ein zwei mal die Bilder animiert anzeigen lasse, sehe ich im taskmanager, dass windows die Auslagerungsdatei auf über 1 GB erhöht und das System deutlich langsamer wird.

Die Bilder sind nach Tagen in Ordnern sortiert, deshalb ist die Bilddatenmenge einer Animation maximal ca. 30 MB groß. Ausserdem werden beim schliessen des Animationsfensters doch alle in den Arbeitsspeicher geladenen Daten wieder entfernt, oder?

Ist es ein Fehler, alle Bilder vorher in ein Bild-Array zu laden und dann anzuzeigen?
Sollte ich die Bilder direkt von der Platte lesen und anzeigen lassen? Das gibt dann doch auch geschwindigkeitsprobleme.

Ich weiss meine Informationen sind vermutlich etwas dürftig, aber vielleicht hat dennoch jemand einen Tip für mich.

Gruß, aVlbger

Wenn du alle Bilder in den Speicher lädst ist klar, dass dein Speicherverbrauch in die Höhe schnellt, würde ich nicht machen.

Ansonsten solltest du darauf achten, dass du Bilder, die du nicht mehr im Speicher benötigst explizit freigibst. Wenn du das dem GC überläßt, kann das Verhalten sehr unangenehm werden. Also Bild-Objekte mit Dispose() wieder freigeben!

Man muss auch bedenken, dass Bilder im Arbeitsspeicher bedeutend mehr Speicher belegen, da sie unkomprimiert vorliegen.

Ein Bild mit der Auflösung 1024*768 und 24 Bit Farbtiefe belegt beispielsweise schon 2,25 Mb. Das Jpeg dazu hat vielleicht 200kb.

Gruss
Pulpapex

Hallo avlbger,

Ausserdem werden beim schliessen des Animationsfensters doch alle in den Arbeitsspeicher geladenen Daten wieder entfernt, oder?

nö, wenn noch andere Verweise auf die Image-Objkete existieren, macht das Schließen des Fensters gar keinen Unterschied und wenn keine anderen Verweise darauf existieren, werden die Objekte erst aus dem Speicher entfernt, wenn der GarbageCollector irgendwann losläuft.

Es sollte aber helfen, Image.Dispose explizit aufzurufen.

herbivore

also, wenn ein 220 Kb jpg im Arbeitsspeicher ca. 2,25 MB belegt, dann ist mir klar warum der dann so voll ist.
Wenn ich also, wenn die Animation beendet ist,für das Array in dem die Bilder sind,die Methode dispose() aufrufe, wird es definitiv aus dem RAM gelöscht, oder?

Hallo avlbger,

man abgesehen davon, dass Array und ArrayList gar kein Dispose haben, solltest du ohnehin Dispose für die einzelnen Image-Objekte aufrufen. Durch Dispose wird aber nicht das Objekt selbst aus dem Speicher entfernt, sondern nur die enthaltenen nicht verwalteten Ressourcen freigegeben. Das .NET-Image-Objekt selbst wird so oder so erst durch den GC entfernt (denn man von Hand starten könnte, aber lies erstmal weiter).

Im Falle der Image-Objekte müsste das eigentliche Bild - also das, was hier den Speicher frisst - ein nicht verwaltetes GDI+ Objekt sein, sodass Dispose mit dessen Freigabe auch den allergrößen Teil des Speichers freigibt und vermutlich nur ein paar Byte Verwaltungsinformationen im Image-Objekt selbst übrig bleiben.

herbivore

Hallo

Ja, ich hab auch grad gemerkt, dass das Array keine Dispose() Methode hat.
Habe jetzt einfach eine for-Schleife eingebaut, die das ganze Array durcharbeitet und für jedes einzelne Image dispose() aufruft.
Laut einem Blick auf meinen Task-Manager klappt das.
Ist aber irgedwie doch auch nicht die wahre Lösung, oder?
Naja, hauptsache mein Speicher muss keine Überstunden mehr schieben 😉

mfg, aVlbger

Die "wahre Lösung" ist, nicht Einzelbilder als Video abzuspielen. Am besten wäre ein streaming-fähiges Videoformat, bei dem ja nur das aktuell angezeigte Frame entpackt wird. Ist auf jeden Fall speicherschonender und braucht weniger CPU-Leistung.

Passt vielleicht nicht unbedingt in diesen Thread, ich denke aber daß es mir besser geht wenn ich darüber geredet habe 😁

Ich habe das Gefühl, daß irgend etwas an der Image-Klasse nicht ganz stimmt. Vielleicht mache ich aber auch wieder nur etwas falsch 🙂
Ich sitze gerade an einem recht schwachen Rechner und habe noch 125 von 256 MB frei nachdem meine Anwendung gestartet ist. Der Versuch mehrere 16x16 Pixel große Icons (je 1 kb) mit Image.FromFile() zu lesen scheitert schon nach dem Lesen des zweiten Icon an einer OutOfMemory-Exception. Was mich wirklich stutzig macht ist, daß ich keine große Abnahme des freien Speichers feststellen kann (es sind immer noch 123 MB frei), aber trotzdem diese Exception bekomme. 🤔

Shaderman

Lange Rede kurzer Sinn:



private ArrayList _ArrImages=new ArrayList();

		private void btnCreateImages_Click(object sender, System.EventArgs e)
		{
			for (int i=0;i<50;i++)
			{
				this._ArrImages.Add(new Bitmap(1024,768));
			}
		}

		private void btnDisposeAndClear_Click(object sender, System.EventArgs e)
		{
			foreach (Image img in this._ArrImages)
			{
				img.Dispose();
			}
			this._ArrImages.Clear();
		}

		private void btnClearAndCollect_Click(object sender, System.EventArgs e)
		{
			this._ArrImages.Clear();
			GC.Collect();
		}


Ihr könnt das Beispiel selber ausprobieren (je nach Memory anzahl Bilder einschränken).

Es ist so wie ich es erwartet habe....

Ein Clear auf der ArrayList löste die letzte Referenz auf das Image.... das Image wurde aber nicht explizit Disposed... wenn jetzt GC.Collect aufgerufen wird, werden die Destruktoren ~ oder Finalizers in VB ausgeführt, was auch dazu führt, dass die Bilder Collected werden (Memory wird freigegeben).

Das mit dem Iterieren und explizitem Aufruf von Dispose für die Bilder funktioniert natürlich auch, da die Dispose-Methode in der Regel so ausgelegt ist, dass bei einem expliziten Call von Dispose sich das Objekt (Bild) mit GC.SuppressFinalize(this) aus der Liste der pending Finalizers austrägt und somit sofort Collected werden kann.

Wenn nur ein Clear auf der ArrayList durchgeführt wird (weder Dispose der Bilder noch GC.Collect), dann sind die Images auch Weak (es existiert nur noch eine Weak-Reference anstelle einer StrongReference).... diese Objekte befinden sich dann in der neuesten Generation des GC und werden nicht sofort, sondern erst nach einer gewissen Zeit Collected... also dessen Ressourcen freigegeben.

Ich finde die Variante mit GC.Collect schöner, jedoch nur dann angebracht, wenn viel Speicher beansprucht wurde (auch ein Collect kostet CPU-Zeit).... bei kleineren ArrayLists usw würde ich es dem GC überlassen wann er Zeit hat abzuräumen.

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

Kleine Bemerkung am Rande: das Beispiel ist Beispielhaft. "die Methode(n) GC. HasteNichJesehn" wirkt natürlich eleganter, ist z.Z meist auch ok da es nicht so viele FrameWorkApps auf einem Rechner gibt. Spätestens mit Longhorn sollte das jedoch aus jedem Programmiererhirn verschwinden, da damit a l l e Anwendungen aufgeräumt werden. Das bedeutet demnach einen enormen Performanceverlust für die eigene Applikation, ein besseres eigentor kann man sich kaum schiessen...
nichts für ungut, aber das scheint nun mal die Realität zu sein.

Wenn ich mich nicht irre, dann sorgt GC.Collect() keineswegs für die sofortige Freigabe aller nicht referenzierten Objekte. Da gabs sowas wie Generationen. Collect() verschiebt meist nur in die nächste Generation. Kann man auch sehr schön im Memory-Profiler sehen, wie nach dem Aufruf von Collect() erstmal NIX passiert.

Daher sollte man bei Ressourcen sich Dispose() nicht sparen, da damit das sichere Abräumen der unmanaged Ressourcen instantmäßig und kontrolliert erfolgt.

Original von ikaros
Kleine Bemerkung am Rande: das Beispiel ist Beispielhaft. "die Methode(n) GC. HasteNichJesehn" wirkt natürlich eleganter, ist z.Z meist auch ok da es nicht so viele FrameWorkApps auf einem Rechner gibt. Spätestens mit Longhorn sollte das jedoch aus jedem Programmiererhirn verschwinden, da damit a l l e Anwendungen aufgeräumt werden. Das bedeutet demnach einen enormen Performanceverlust für die eigene Applikation, ein besseres eigentor kann man sich kaum schiessen...
nichts für ungut, aber das scheint nun mal die Realität zu sein.

Ach, wer sagt denn das? Hast Du schon mehere managed Applications auf
WindowsLonghorn ausgeführt?
Wie groß ist denn der Performance-Verlust?

Gernot Melichar

Original von svenson
Wenn ich mich nicht irre, dann sorgt GC.Collect() keineswegs für die sofortige Freigabe aller nicht referenzierten Objekte.

Du irrst Dich aber.... GC.Collect() ohne Parameter "Forces garbage collection of all generations" so stehts im MSDN drinne....

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

Ja, korrekt - mein Fehler. Allerdings werden beim ersten Collect()-Aufruf für alle Objekte, die IDisposable implementieren nur Dispose() aufgerufen (würde in diesem Szenario reichen). Erst beim nach GC-Durchgang wird auch der managed Speicher abgeräumt.

Komplett abräumen geht mit:


  GC.Collect( );
  GC.WaitForPendingFinalizers( );
  GC.Collect( );

Ich halte diese Lösung trotzdem für schlecht, da sie vom Laufzeitverhalten zu Verzögerungen führen kann, während Dispose() eine besseres Verhalten zeigen sollte.

Original von svenson

Komplett abräumen geht mit:

  
  GC.Collect( );  
  GC.WaitForPendingFinalizers( );  
  GC.Collect( );  
  

Ich halte dies für übertrieben, da die Dispose-Methode der einzelnen Objekte im Regelfall so implementiert sein sollten:


private void CleanUp()
{
//rufe Dispose der verwendeten Objekte auf
//lösche die Variablen
}

public void IDisposable.Dispose()
{
//rufe internen CleanUp auf
this.CleanUp();
//schliesse das Objekt von der Finalisierung aus, da schon alles geräumt wurde
GC.SuppressFinalize(this);
}

~MyObject()
{
this.CleanUp();
}

Ein explizites aufrufen von Dispose disposed abhängige Objekte und gibt deren StrongReference frei. Anschliessend wird das Objekt aus der Liste der PendingFinalizers entfernt (ein Aufruf des Destructors ist nicht mehr nötig)....

Wenn Dispose nicht explizit aufgerufen wird, dann bleibt das Objekt in der Liste der Pending Finalizers.... somit ruft der GC dessen Destructor auf, welcher intern dieselbe Methode aufruft (CleanUp) wie bei einem Dispose aufgerufen würde.

Also wenn schon WaitForPendingFinalizers dann so:


GC.WaitForPendingFinalizers();
GC.Collect();

Wenn ich mich richtig erinnere ging es ja ursprünglich um eine Collection von IDisposable-implementierenden Objekten in Zusammenhang mit der Freibage des Memory. Ob nun ein Aufruf jeder einzelnen Dispose-Methode innerhalb eines Loops oder der einmalige Aufruf von GC.Collect() zeitintensiver ist spielt meines Erachtens nicht so eine Grosse Rolle, da beides ungefähr gleich viel Zeit kosten wird..... Und falls es wirklich so sein sollte, dass GC.Collect alle Objekte aller Domains räumen sollte, so wird dies zwar länger dauern als wenn man gezielt Objekte abschiesst, dies wird jedoch auch in mehr freiem Speicher enden, was letzlich auch unserer Memoryintensiven Anwendung zu gute kommt.

Last but not Least:

Stelle Dir mal folgendes Szenario vor:

Du hast diese Collection mit den IDisposable-Objekten drin. (30 Objekte a 10 MB). Die Collection wird verworfen und intern für jedes Objekt Dispose aufgerufen. Ich finde dies insofern gefährlich, da die Collection ja gar nicht wissen kann, ob eines ihrer verwalteten Objekte noch referenziert ist (ob also aus einem anderen Programmteil noch eine Referenz auf so ein Objekt besteht).... Derjenige welcher noch eine Instanz eines solchen Objektes hält wird nicht erfreut sein, wenn er eine ObjectDisposed-Exception erhält (ich glaube so heisst es).....

Wenn aber nur ein Collection.Clear durchgeführt wird, dann werden nur die nicht mehr referenzierten Objekte freigestellt und falls nötig (bei so grossen Objekten wie in diesem Beispiel) durch GC.Collect sofort geräumt...... das einzelne noch immer referenzierte Objekt bleibt somit am Leben und kann weiter verwendet werden....

Wie Du siehst bin ich halt der Praktiker, welche im Zweifelsfall lieber den sicheren Weg wählt als den theoretisch saubereren Weg..... In der Praxis nützt es mir sehr wenig, wenn ich Komponenten baue, welche "theoretisch funktionieren"..... ich verwende Ansätze, welche auch die harte Praxis überstehen trotzdem das Memory sauber halten (auch wenn ein GC.Collect nichts ist, was ich täglich verwende).

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

Die Verwendung von GC-Collect() macht SW m.E. wartungsunfreundlich.

Stell dir vor, du hast eine App, in der irgendwo ein Speicherfresser steckt. Jetzt baust du schön GC-Collect() ein und alles ist gut.

Jetzt erweiterst du die Software und hast nen 2. Speicherfresser, der aber nicht synchron läuft. Wieder GC-Collect() mit dem Effekt, dass du beide Speicherräume abräumst mit der entsprechenden ("verdoppelten" Verzögerung).

Was ich damit sagen will: GC.Collect() ist eine gobale "Kanone", die auf jeden Speicherblock schießt, der nicht bei drei eine Refernz vorweisen kann.

In komplexen Speicherszenarien ist das einer selektiven Freigabe einfach unterlegen. Also lieber gleich mit Dispose() arbeiten.

Hallo Programmierhans,

ich kann deine Überlegung mit den disposten, aber noch referenzierten Objekten grundsätzlich nachvollziehen. Aber mit deiner Argumentation stellst du den Sinn von Dispose grundsätzlich in Frage. Was, wenn die Objekte in der Collection keine Bilder sind, sondern geöffnete Streams? Würdest du die schließen, indem du den CG benutzt? Oder wenn es die Fenster einer Anwendung sind? Kein Close (welches ja Dispose ausführt), sondern GC? Wenn es aber in diesen Fällen ok ist, Close/Dispose zu verwenden, dann ist es das m.E. auch für Images.

Ich sehe die Nachteile von Dispose (man gebe mir die C++-Destruktoren zurück 🙂 aber in mir sträubt sich alles gegen den GC.

herbivore

Nö versteht mich bitte nicht falsch.... Ich bin absolut kein Gegner von Dispose.... Ich habe dieses Beispiel immer aus Sicht der Collection betrachtet.... ich bin nachwievor überzeugt, dass die Collection die verwalteten Objekte nicht disposen darf.... anders sieht es natürlich aus, wenn die Objekte vom aufrufenden Code disposed werden....

böse: X(


protected override void Clear()
{
foreach (IDisposable i in base.Values)
{
i.Dispose();
}
base.Clear();
}

ok:


foreach (IDisposable i in Collection.Values)
{
i.Dispose();
}
Collection.Clear();

Mal angenommen ein Entwickler einer Firma entwickelt Komponenten.... ein anderer verwendet die Komponenten.... dann darf derjenige welcher die Komponenten entwickelt kein Dispose durchführen..... er kann nur mit GC.Collect das schlimmste verhindern (was auch nicht ideal ist).... denn derjenige welcher die Collection füllt soll diese auch selber wieder sauber abräumen....

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

Wichtig zu wissen ist, daß fast alle GDI+ Objekte mit Dispose wieder freigegeben werden müssen!

Original von Programmierhans
Nö versteht mich bitte nicht falsch.... Ich bin absolut kein Gegner von Dispose.... Ich habe dieses Beispiel immer aus Sicht der Collection betrachtet.... ich bin nachwievor überzeugt, dass die Collection die verwalteten Objekte nicht disposen darf....

Ich denke, da sind wir uns absolut einig. Wäre extrem fatal.