Problemy z pamięcią. NET Ładowanie ~ 40 obrazów, pamięć nie odzyskana, potencjalnie z powodu fragmentacji LOH

[7]} Cóż, to jest mój pierwszy wypad do profilowania pamięci aplikacji. NET (CPU tuning zrobiłem) i uderzam trochę ściany tutaj.

Mam widok w mojej aplikacji, która ładuje 40 obrazów (max) na stronę, każdy działa około ~3MB. Maksymalna liczba stron to 10. Ponieważ nie chcę jednocześnie przechowywać 400 obrazów lub 1,2 GB w pamięci, ustawiam każdy obraz na null po zmianie strony.

Na początku myślałem, że muszę mieć tylko czerstwe odniesienia do tych obrazów. Ściągnąłem ANTS profiler (świetne narzędzie BTW) i przeprowadził kilka testów. Obiekt lifetime graph mówi mi, że nie mam żadnych odniesień do tych obrazów poza pojedynczym odniesieniem w klasie nadrzędnej (co jest projektowane, również potwierdzone przez skrupulatne przeczesywanie mojego kodu): {]}

Tutaj wpisz opis obrazka

Klasa nadrzędna SlideViewModelBase pozostaje na zawsze w buforze, ale właściwość MacroImage jest ustawiona na null, gdy strona jest zmieniana. Nie widzę żadnych wskazań, że te przedmioty powinny być trzymane dłużej niż oczekiwane.

Następnie przyjrzałem się stercie dużych obiektów i zużyciu pamięci w ogóle. Po obejrzeniu trzech stron zdjęć mam 691,9 MB niezarządzanej pamięci przydzielonej i 442,3 MB na LOH. System.Byte[], która pochodzi z mojego System.Drawing.Bitmap do BitmapImage konwersja zajmuje prawie całą przestrzeń LOH. Oto Mój kod konwersji:

public static BitmapSource ToBmpSrc( this Bitmap b )
{
    var bi = new BitmapImage();
    var ms = new MemoryStream();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    b.Save( ms,  ImageFormat.Bmp );
    ms.Position = 0;
    bi.BeginInit();
    ms.Seek( 0, SeekOrigin.Begin );
    bi.StreamSource = ms;
    bi.EndInit();
    return bi;
}
Ciężko mi znaleźć, dokąd zmierza ta niezarządzana pamięć. Na początku podejrzewałem obiekty System.Drawing.Bitmap, ale mrówki ich nie pokazują zatrzymałem się, a ja również przeprowadziłem test, w którym upewniłem się, że wszystkie zostały usunięte i to nie robi różnicy. Więc jeszcze nie wiem, skąd bierze się ta niezarządzana pamięć.

Moje dwie obecne teorie to:

  1. LOH Jeśli odejdę od widoku paged i kliknę kilka przycisków, około połowa ~1.5 GB zostanie odzyskana. Wciąż za dużo, ale ciekawe.
  2. jakieś dziwne Wiązanie WPF. Używamy databinding do wyświetlania tych obrazów i nie jestem ekspertem w odniesieniu do tajników, jak te kontrole WPF działają.

Jeśli ktoś ma jakieś teorie lub wskazówki dotyczące profilowania, byłbym niezmiernie wdzięczny, ponieważ (oczywiście) jesteśmy w napiętym terminie i trochę się spieszę, aby zrobić tę ostatnią część i pracować. Myślę, że zostałem rozpieszczony przez śledzenie wycieków pamięci w C++ ... kto by pomyślał?

Jeśli potrzebujesz więcej informacji lub chcesz, żebym spróbował czegoś innego, zapytaj. Przepraszam za ten tekst, starałem się, żeby był jak najbardziej zwięzły.

Author: Ed S., 2011-06-08

2 answers

Ten wpis na blogu wydaje się opisywać to, co widzisz, a proponowanym rozwiązaniem było stworzenie implementacji strumienia, która owija inny strumień.

Metoda Dispose tej klasy wrapper musi zwolnić owinięty strumień, aby mógł być zbierany. Po zainicjowaniu obrazu bitowego tym strumieniem wrappera, strumień wrappera może zostać usunięty, zwalniając podstawowy strumień i pozwalając na to, aby duża tablica bajtów była uwolniony.

BitmapImage utrzymuje odniesienie do strumienia źródłowego, dzięki czemu utrzymuje obiekt MemoryStream przy życiu. Niestety, mimo że MemoryStream.Dispose został wywołany, nie zwalnia tablicy bajtów, którą zawija strumień pamięci. Tak więc w tym przypadku bitmap jest odwołującym się strumieniem, który jest odwołującym się buforem, który może zajmować dużo miejsca na dużej stercie obiektów. Nie ma prawdziwego wycieku pamięci; gdy nie ma już odniesień do bitmapy, wszystkie te obiekty będą (ostatecznie) być zbierane śmieci. Ale ponieważ bitmap ma już własną prywatną kopię obrazu (do renderowania), wydaje się raczej marnotrawstwem mieć teraz niepotrzebną oryginalną kopię bitmapy nadal w pamięci.

Również, jakiej wersji. NET używasz? Przed. NET 3.5 SP1, był znany problem, gdzie BitmapImage może spowodować wyciek pamięci . Obejście polegało na wywołaniu Freeze Na BitmapImage.

 35
Author: Oppositional,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-07 21:50:20

Gdzie zamykasz i usuwasz strumień pamięci? Może być tak, że GC musi pracować o wiele ciężej, aby uwolnić zasoby, przesuwając kilka pokoleń w górę przed wykonaniem destruktorów na obiekcie (które zazwyczaj nazywają dispose, jeśli zapomniałeś tego zrobić).

W Twoim przypadku nie możesz pozbyć się strumienia pamięci, dopóki nie skończysz z obrazem. Gdy chcesz je rozładować, przeprowadź pętlę przez obrazy i spróbuj usunąć strumień pamięci.

 2
Author: Dave Ferguson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-07 21:41:13