Laden...

Timer müllt Speicher zu

Erstellt von Sharpovski vor 18 Jahren Letzter Beitrag vor 18 Jahren 3.894 Views
S
Sharpovski Themenstarter:in
259 Beiträge seit 2004
vor 18 Jahren
Timer müllt Speicher zu

Hallo zusammen,

habe hier folgenden kleinen Timer gebastelt, der jede Sekunde anspringt:

private TimeSpan hour = new TimeSpan(1, 0, 0);
private TimeSpan second = new TimeSpan(0, 0, 1);

private void timer_Tick(object sender, System.EventArgs e)
{
	timer.Stop();
	hour = hour.Subtract(second);
	label.Text = ts.TotalMinutes;
	timer.Start();
}

Wenn ich den jetzt laufen lassen, steigt die Speicherauslastung im Task-Manager mit der Zeit. Woran kann das liegen?

F
10.010 Beiträge seit 2004
vor 18 Jahren

Du erzeugst Ständig neue Objekte, die erst "weggeräumt" werden, wenn der GC
anfängt Aufzuräumen.

Das passiert aber erst, wenn Windows sagt das der Speicher eng wird.

6.862 Beiträge seit 2003
vor 18 Jahren

warum Stops und Startest du deinen Timer jedes mal, lass ihn doch durchlaufen mit der entsprechenden TickTime.

Baka wa shinanakya naoranai.

Mein XING Profil.

S
Sharpovski Themenstarter:in
259 Beiträge seit 2004
vor 18 Jahren

Wenn die Routine mal etwas länger dauert, könnte es sein, dass sich diese Überholen. Damit es da keine Probleme gibt, stoppe ich den Timer lieber.

C
192 Beiträge seit 2005
vor 18 Jahren

Original von FZelle
Du erzeugst Ständig neue Objekte, die erst "weggeräumt" werden, wenn der GC
anfängt Aufzuräumen.

Das passiert aber erst, wenn Windows sagt das der Speicher eng wird.

Wo erzeugt er denn objekte?

B
483 Beiträge seit 2005
vor 18 Jahren

Hallo,

sicher bin ich nicht: zu wenig Code, aber es sieht nach dem Konflikt zwischen Stop-Methide und Elapsed-Erreignis oder Endlosschleife, würde nicht auf GC tippen.

Gruss,
Boris

S
8.746 Beiträge seit 2005
vor 18 Jahren

Ich hab mich mal ein wenig über das Starten des GC schlau gemacht. Die Aussage "GC läuft an, wenn der Speicher knapp wird", kann man so stehen lassen, aber die Frage "wann ist der Speicher knapp", beantwortet .NET (nicht Windows!) mit einem geheimen, "selbst-optimierenden" Algorithmus. Generell hängt es von der Allokations-Rate der Applikation ab.

Darüberhinaus findet bereits vorher mindestens eine Collection statt, nämlich beim Vollaufen von Gen0. Die Max-Size von Gen0 entspricht dem L2-Cache des Prozessors (512kb-2Mb).

Bei Gen1 gibt es "Gerüchte", die besagen, dass hier maximal die Hälfte der GC-Segment-Größe verwendet wird, das wären dann zwischen 8 und 32 MB.

Hier was zu den Triggern:

"Most information about the managed heap generation indicates their sizes as follows:

* Generation 0 starts off with a threshold around 256 kB  
* Generation 1 starts off with a threshold around 2 MB  
* Generation 2 starts off with a threshold around 10 MB  

However information from Microsoft CLR engineers suggests that the generation 0 and generation 1 thresholds start out at different levels. The generation 0 threshold defaults to the size of the L2 on-chip cache (also called the Level 2 cache, or secondary cache). The initial minimum threshold for generation 1 is about 300kB, whereas the maximum size can be half the segment size, which for the regular single processor workstation GC will amount to 8MB. The plan being that most generation 0 allocations (i.e. managed objects) will live and die entirely on the CPU chip in the very fast L2 cache.

The garbage collector, when operating off its own bat, keeps an eye on how the application is allocating memory (through object construction). If necessary it will modify the thresholds of each of the managed heap generations. The second reason for not manually invoking the garbage collector is that this will break its statistical analysis of the program, which it uses to make decisions on any fine-tuning of these thresholds."

Gen2 ist generell unbeschränkt, was nicht heissen muss, dass erst bei 0 Byte freiem, virtuellen Speichern der GC losrennt (initial eben bereits bei 10MB).

Speicherblöcke jenseits von 80KB landen zudem immer in Gen2. Große Mengen großer Speicherblöcke zu allokieren kann also recht schnell zu einem vollem Speicher führen.

Generell führt eine GC dazu, dass Geno0 anschliessend leer ist, benutze Objekte werden in Gen1 befördert. Der GC wird also recht häufig angeworfen, aber solange Gen0 und Gen1 Platz haben, werden auch Gen2-Objekte nicht entfernt.

Kritisch sind also Anwendungen, die hie und da große managed Speicherblöcke allokieren, sonst aber nur kleine, kurzzeitige Objekte allokieren. Hier wird praktisch nie eine GC in Gen2 durchgeführt und Speicherbedarf geht bis zum "Anschlag", bevor dann GC in Gen2 zuschlägt. Von händischem GC.Collect() wird aus o.g. Gründen abgeraten.

Ein weiteres Problem resultiert aus Multi-Threading. Steht ein Thread (A) aufgrund einer blockierenden Operation (DB-Abfrage, etc.) und löst ein anderer Thread (B) in dieser Zeit den GC für Gen1 aus, so werden auch alle Objekte vom Thread A befördert. Das führt dazu, dass Gen1-Objekte von Thread A in Gen2 wandern und damit dem GC für lange Zeit "entwischen", auch wenn sie sonst durch den nächsten Gen1-GC abräumt worden wären (Zeitpunkt des Null-Setzen ist deutlich vor des Verlassens des Scopes). Genau an diesem Punkt macht das ominöse Setzen der nicht mehr benötigten Referenzen auf Null Sinn, weil damit die Beförderung verhindert wird.

Merke: Auch wenn der GC öfter läuft als man denkt, führt das u.U. trotzdem dazu, dass der Speicherverbrauch unaufhaltsam ansteigt (obwohl die Objekte durchaus freigegeben werden könnten) um dann en bloc wieder auf unter 40MB (Gen0 + Gen1 + Gen2-Rest) zu sinken.

Um jetzt auf den Code zurückzukommen:

Hier sollten die Objeke ausschießlich in Gen0 landen, die auch sofort wieder abgeräumt werden. Insofern ist das Problem nicht im Timer zu vermuten. Spannend sind immer Beförderungen nach Gen2. DIese lassen den Speicherbedarf anschwellen. Hier mal mit einem Memory-Profiler zu schauen wäre sinnvoll.

I
1.739 Beiträge seit 2005
vor 18 Jahren

Ich hab mich mal ein wenig über das Starten des GC schlau gemacht. Die Aussage "GC läuft an, wenn der Speicher knapp wird", kann man so stehen lassen, aber die Frage "wann ist der Speicher knapp", beantwortet .NET (nicht Windows!) mit einem geheimen, "selbst-optimierenden" Algorithmus. Generell hängt es von der Allokations-Rate der Applikation ab.

Ja bis auf den letzten Satz, der ist falsch. Der GC arbeitet alle Applikationen ab(.Net Apps nat.) wenn er sich bequemt aktiv zu werden. Das ist einer der Gründe warum man sich normalerweise drum drücken sollte, den GC aus einer eigenen App zu aktivieren(nat. nur wenn es sein könnte das mehr als eine (die Eigene .Net- App) auf dem Zielsytem laufen könnte(meist kein echter Grund und nur Performancebremse)

F
10.010 Beiträge seit 2004
vor 18 Jahren

@cmpxchg:
Der o.a. Code erzeugt folgende Objekte, die immer wieder neuen Speicher benötigen bis der GC sie wegräumt:


hour = hour.Subtract(second);
// Ein objekt vom Typ TimeSpan
label.Text = ts.TotalMinutes;
// Einen String

4.221 Beiträge seit 2005
vor 18 Jahren

timer.stop / start generiert auch jede menge objekte

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

S
8.746 Beiträge seit 2005
vor 18 Jahren

Original von ikaros

Der GC arbeitet alle Applikationen ab(.Net Apps nat.) wenn er sich bequemt aktiv zu werden.

Woher hast du denn die Info? Meines Wissens hat jeder Runtime-Host seinen eigenen GC, der entweder im eigenen Thread (concurrent) oder im User-Thread ausgeführt wird.

I
1.739 Beiträge seit 2005
vor 18 Jahren

Sorry für die späte Antwort.

Habe ich beim "Umstieg" gelesen (Wahrscheinliche Quellen: DotNet Pro, DotNet Magazin oder eins meiner ersten Bücher zu .Net), war Anfang 2004.
Ich seh mal nach, vielleicht find ich die Quelle. Die Chancen sind glaube ich gut.
(wird aber ein paar Tage dauern, wg. wenig Zeit)
Ich halte es durchaus für möglich, das die Aussage nicht korrekt ist. Jedenfalls hat sie mich davon abgehalten den GC aus meinen Apps heraus zu zwingen aktiv zu werden.

Hast du deinerseits Quellen? So ganz uninteressant die Thematik ja auch nicht.