Thomas Woelfers Baustatik Blog

Baustatik-Software und was sonst des Weges kommt

Merkwürdigkeiten beim .Net Garbage Collector

Irgendwie scheint der .Net 2.0 Garbage-Collector einige Dinge anders zu tun, als ich bisher gedacht habe. Vermutlich liegt das daran, das ich irgendwas falsch mache, aber zur Zeit sieht es eher danach aus, das er auf einer Single-Core Maschine nicht richtig funktioniert... Zur Erklärung hier ein paar Graphiken:

Die drei Graphiken geben den Informationen über den Speicherverbrauch der Baustatik im Zuge von etwa 350 durchgeführten Testcases wieder. Jedes Pixel in X steht dabei für einen Testcase. (Die dritte Graphik ist ein bisschen kürzer, weil aufgrund von Speichermangel nicht alle Testcases durchgeführt werden können - das Programm stürzt mit einer OutOfMemory Exception ab.)

Das Programm arbeitet also eine Reihe von Fällen ab. Am Ende jedes Falles werden 3 Daten erhoben:

  • Die Speicherauslastung bezogen auf den verfügbaren Speicher. (Blaue Kurve)
  • Das Working-Set (Grüne Kurve [ Environment.WorkingSet ])
  • Die Speichermenge, die der GarbageCollector verwaltet. (Rote Kurve [GC.GetTotalMemory(false) ])

In allen drei Fällen wurde das exakt gleiche Binary und die exakt gleichen Testcases durchgeführt. Die erste Graphik kam dabei auf einem Quad-Core System zustande, die zwei auf einem Dual-Core und die dritte auf einem Single-Core System. Wie man leicht sehen kann, gibt es einen unmittelbaren Zusammenhang zwischen dem Working-Set und dem GC-Speicher. Was man auch sehen kann ist die Tatsache, das sich das Programm auf den beiden Multi-Core Systemen exakt so verhält, wie man es erwarten würde: Zwar gibt es an einigen Stellen ein Wachstum im Speicherbedarf, am im großen und ganzen ist der konstant - durchbrochen von leichten Sprüngen, die daraus resultieren, das der GC hin- und wieder eingreift und Speicher freigibt.

Soweit, sogut. Nicht so gut: Auf der Single-Core Maschine wird offensichtlich überhaupt kein Speicher freigegeben. Es wird immer mehr und mehr alloziert, bis das Programm schließlich per OutOfMemory Exception stirbt.

Nun war eine erste (nicht ganz unplausible) Vermutung die, das der GC-Thread auf der Single-Core Maschine einfach nie "dran" kam, weil das durchführen der Testcases einfach zu viel Last machte. Darum habe ich zu Testzwecken nach der Durchführung jedes Testcases und vor dem ermitteln der Speicherauslastung noch folgenden Code eingebaut:

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

Das sollte nach meinem dafürhalten und auf Basis der Dokumentation dazu führen, das auf jeden Fall ein Collect über alle Generationen durchgeführt und auch Finalizer aufgerufen werden. Und auf den Multicore-Systemen hat das auch die erwarteten Auswirkungen: (Hier nur eines der beiden Bilder)

Wenn man hier den Speicher ansieht, der dem GC unterliegt (rote "Kurve"), dann verhält sich er so wie man das erwarten würden: Nach jedem Collect() wird die gleiche Menge benötigt. Dummerweise sieht das auf der Single-Core Maschine anders aus:

Hier wird weiter fröhlich Speicher alloziert und nicht wieder freigegeben - und zwar noch schneller, als ohne das Collect() - die Anwendung stürzt noch deutlich früher mit einer OutOfMemory Exception ab.

Wäre sehr dankbar, wenn mir das jemand erklären könnte.

Comments (2) -

  • Sensei76

    11/17/2008 8:47:51 AM |

    Eine echte Lösung kann ich leider auch nicht bieten.

    Aber meine erste Frage für den Single-Core Rechner wäre erst einmal ganz banal, ob auf diesem Rechner die selbe Version des .NET Frameworks installiert ist und ob sich die installerten Patches, Hotfixes und ServicePacks auf den verschiedenen Testsystemen unterscheiden.

    Unter Umständen würde ich es vielleicht auch mal mit mit gezielten Aufrufen von dem (bei uns im Büro eher verpönten) Application.DoEvents() versuchen, wenn der Thread auf dem Single-Core einfach nicht zum Zug kommt.

    Ansonsten wäre ich auch sehr an einer Erklärung interessiert.

  • thomas woelfer

    11/17/2008 8:53:59 AM |

    Danke für den Hinweis - allerdings hat das leider nichts geholfen. Tatsächlich ist es so, das ich am WE per Profiler auf einer der Single-Core Maschinen nach dem Leck gesucht habe - und es auch gefunden habe. Nach der Beseitigung des Lecks trat das Problem dann auch nicht länger auf. Bleibt die Frage: Warum trat das Leck auf den Multicore Maschinen _nicht_ auf, bzw: warum wurde es nicht sichtbar. Ist mir weiterhin schleierhaft. Davon ab gibt es hier ( social.msdn.microsoft.com/.../c762207c-5b71-45f4-81fa-84647c77b5ed ) eine ganz interessante Diskussion zum Thema, mit ganz interessanten Hinweisen (auch wenn sie in diesem Fall nichts geholfen haben), die u.a. jemand anderem mal helfen können. Smile

Comments are closed