Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Visual C# - zużycie RAM przez aplikację

marcin w 19 May 2012 10:39 2877 9
  • #1
    marcin w
    Level 22  
    Witam

    Jako że poznaję C#, napisałem prosty nic nie znaczący programik. Po jego uruchomieniu obserwowałem jak wygląda proces w pamięci i ile zajmuje miejsca. Sam programik jest banalny, na formie dwa komponenty, Button oraz CheckBox. Funkcjonuje on tak, że po kliknięciu w Button zaznaczana lub odznaczana jest opcja w CheckBox (w zależności od stanu aktualnego)



    Wygląda on tak:
    Code: csharp
    Log in, to see the code

    Program dobrze działa, lecz zauważyłem wzrost zużycia RAM-u przez aplikację po każdym kliknięciu buttona. Każdorazowe kliknięcie w niego zwiększa zużycie pamięci o ok 20 - 30 kB. Nawet po czasie pamięć nie jest zwalniana i program po każdym kliknięciu rozrasta się w RAM-ie.

    Skąd wynika taka niedogodność ?
  • #2
    marcinj12
    Level 40  
    Nie jest to zjawisko, którym powinieneś się przejmować - taka "uroda" platformy .NET.
    Do uruchomienia na niej programu potrzeba "w tle" załadować i uruchomić całe środowisko uruchomieniowe (CLR), na którym wykonywany jest program. Zarówno ono jak i sam program ma swoje potrzeby na pamięć - np. być może sprawdzenie warunku if tworzy w pamięci tymczasową zmienną. Jeżeli użyjesz do obserwacji bardziej zaawansowanego narzędzia niż windowsowy Manedżer Zadań (np. Process Explorer) zaobserwujesz, że nawet tak prosta aplikacji uruchamiana jest na kilku wątkach (ja zaobserwowałem 5-7 wątków), każdy z nich coś tam sobie z pamięcią robi po swojemu... Zauważ też, że nie każde kliknięcie przycisku powoduje przyrost pamięci, z początku, jak się program "rozgrzewa", przyrost jest większy, potem stopuje na dłuższą chwilę a potem cyklicznie są okresy kiedy przyrasta / nie przyrasta.

    Wracając do sedna: ustalenie co dokładnie powoduje ten przyrost pamięci jest o tyle bardzo trudne co do niczego niepotrzebne - w tle cały czas działa tzw. Garbage Collector (GC), odpowiedzialny za niszczenie niepotrzebnych zmiennych i odzyskiwanie z pamięci wolnego miejsca działającej aplikacji .NET. Jest on sterowany przez środowisko uruchomieniowe i włącza się według własnego, raczej nieprzewidywalnego, algorytmu. Jest to dosyć pracochłonny proces, więc sprzątanie włączy się tylko wtedy, kiedy CLR uzna, że bilans zysków jest opłacalny. Jednak zawsze dba on o to, żeby posprzątać zanim zabraknie pamięci. Jestem pewien że gdybyś klikał wystarczająco długo zaobserwował byś jego działanie: program zajął by w pamięci np. 30MB, a po chwili spadł np. do 15MB.

    Jedyne, czym się musisz przejmować pisząc aplikacje pod .NET, to zwalnianie pamięci po obiektach, które tego wymagają, aby nie doprowadzić do wycieków pamięci, czyli w skrócie: jeżeli klasa implementuje interfejs IDisposable (obiekt ma metodę .Dispose()), w dobrym tonie jest ją wywołać - najlepiej używając obiektu w ramach using { ... }. Dotyczy to w szczególności: obiektów baz danych (connection, command, datareader), odczytu/zapisu plików lub innych danych za pomocą readerów i writerów z przestrzeni System.IO (StreamReader, TextReader etc.), obiektów GDI (fonts, brushes, pens etc) oraz obiektów niezarządzanych (np. bibliotek COM gdzie procedura sprzątania jest wyjątkowo upierdliwa).
  • #3
    mickpr
    Level 39  
    marcinj12 wrote:
    Zarówno ono jak i sam program ma swoje potrzeby na pamięć - np. być może sprawdzenie warunku if tworzy w pamięci tymczasową zmienną.

    Sprawidziłem - wycinając całą treść funkcji (button1_Click) - samo "klikanie" w button powoduje identyczny efekt (VS Express 2010).
  • #4
    marcin w
    Level 22  
    OK, dzięki za informację, ten przykład jest banalnie prosty, aplikacja zajmuje w RAM ok 10 MB i przy tej wielkości nie ma problemu z RAM-em. Sądziłem że proces odśmiecania pamięci działa trochę inaczej na bieżąco usuwając zbyteczne pozostałości i śmieci. Nie wiedziałem jak byłoby przy większym i bardziej skomplikowanym programie, gdyby aplikacja zaczęła pożerać znacznie więcej pamięci, powiedzmy powyżej 500 MB.

    Ale jeśli piszecie by się tym nie przejmować to OK.
  • #5
    gaskoin
    Level 38  
    Najpierw nie na temat :)

    Code: csharp
    Log in, to see the code


    Można zapisać to tak:
    Code: csharp
    Log in, to see the code


    Garbage Collector nie zwalnia pamięci natychmiast gdy jest ona niepotrzebna. Nie chcę się tu się zbytnio rozpisywać na ten temat, ponieważ jest on wbrew pozorom bardzo obszerny i zasada działania dość skomplikowana. W standardowym trybie (bo można go nieco zmienić) śmieciarz działa konkurencyjnie. Oznacza to mniej więcej tyle, że na czas sprzątania blokuje on inne wątki i jeśli np na ekranie "coś się dzieje" to na pewno on nie posprząta.
    Można wymusić na nim sprzątanie, ale i to nie daje 100% pewności, że posprząta kiedy chcemy :) Na pewno doda do kolejki sobie fakt, że chcemy posprzątać i zrobi to w wolnym czasie, ale niekoniecznie wtedy kiedy byśmy tego chcieli. Na pewno będzie sprzątał dość intensywnie gdy będziemy się zbliżali do OutOfMemoryException.

    Możesz spróbować zobaczyć, co się będzie działo gdy wywołasz:

    Code: c
    Log in, to see the code


    Pamiętaj, że być może istnieje wyciek pamięci. Może on niekoniecznie zostać zrobiony przez Ciebie, a przez twórców biblioteki .Net.
  • #6
    mickpr
    Level 39  
    gaskoin wrote:
    Pamiętaj, że być może istnieje wyciek pamięci. Może on niekoniecznie zostać zrobiony przez Ciebie, a przez twórców biblioteki .Net.

    Jeśli tak - to wiadomo dlaczego .NET nie poleci w kosmos.
    Świat idzie w ilość - nie w jakość (niestety).
  • #7
    marcinj12
    Level 40  
    gaskoin wrote:
    checkBox1.Checked == !checkBox1.Checked
    Oczywiście miał być jeden znak równości, nieprawdaż? ;P

    Zabawy z GC są generalnie odradzane - jak Kolega zauważył temat jest dosyć obszerny i uzależniony od wielu czynników i, koniec końców, można najwyżej sobie pogorszyć wydajność zwalniając pamięć w niepotrzebnym momencie. Zakładać, że jako użytkownik platformy napiszę sobie lepszy i wydajniejszy mechanizm zwalniania pamięci niż jej twórcy jest trochę... optymistycznym podejściem...

    gaskoin wrote:
    Pamiętaj, że być może istnieje wyciek pamięci. Może on niekoniecznie zostać zrobiony przez Ciebie, a przez twórców biblioteki .Net.
    Słowo być może powinno być tu wytłuszczone, podkreślone i pisane przez duże B. Ja wychodzę z założenia, że o ile na 100% tego faktu wykluczyć nie można, to jednak albo ufa się technologii z której się korzysta - albo nie.
    .NET to nie jakiś opensourcowy projekt tworzony przez garstkę zapaleńców, gdzie każdy może wrzucić co mu się żywnie podoba, a używana na całym świecie platforma tworzona i kontrolowana przez ludzi którzy się na tym znają i którym za to płacą niemałe pewnie pieniądze. Powiem szczerze - zdziwił bym się bardzo, gdyby coś takiego jak wyciek pamięci w podstawowych przecież bibliotekach, w rozwiązaniu ewoluującym od kilkunastu już lat, miał miejsce. A nawet jeśli - na pewno ktoś by go dostrzegł i zgłosił już wcześniej.

    marcin w wrote:
    Nie wiedziałem jak byłoby przy większym i bardziej skomplikowanym programie, gdyby aplikacja zaczęła pożerać znacznie więcej pamięci, powiedzmy powyżej 500 MB.
    Nigdy nie zdarzyła mi się taka sytuacja, robiłem kiedyś testy z których wynikało, że przy konfiguracji sprzętowej na której program działał, pamięć nigdy nie przekroczyła coś między 50 a 300MB (niestety, dokładnych wyników już nie pamiętam...).

    Jeżeli Ciebie lub któregoś z Kolegów interesuje dokładne poznanie natury wspomnianego zjawiska, to są programy zwane memory profiler, które pozwalają podejrzeć co i jak aplikacja w pamięci alokuje/zwalnia. Niemniej, są one w większości płatne, niezbyt przyjemne w obsłudze i wyciąganiu wniosków - jednak jeżeli ktoś ma chęć, może sobie sprawdzić czemu ta pamięć się zwiększa. :)
    Zauważcie też, że jest to forma która wymaga co jakiś czas narysowania / odrysowania czy obsługuje komunikaty np. od ruchu myszką, to też zajmuje miejsce w pamięci i nawet bez klikania przycisku może powodować zmiany w ilości zajmowanej pamięci.
  • #8
    mickpr
    Level 39  
    marcinj12 wrote:

    checkBox1.Checked == !checkBox1.Checked
    Oczywiście miał być jeden znak równości, nieprawdaż? ;P

    Oczywiście - że NIE.
    Powinno być oczywiście tak - jak podał kolega gaskoin.
    Porównanie tak jak napisałeś nie ma sensu.
    Przypisanie wartości negatywnej - jak najbardziej.
  • #9
    gaskoin
    Level 38  
    marcinj12 wrote:
    gaskoin wrote:
    checkBox1.Checked == !checkBox1.Checked
    Oczywiście miał być jeden znak równości, nieprawdaż? ;P
    już poprawiłem, zostało przy kopiowaniu :)

    marcinj12 wrote:
    Powiem szczerze - zdziwił bym się bardzo, gdyby coś takiego jak wyciek pamięci w podstawowych przecież bibliotekach, w rozwiązaniu ewoluującym od kilkunastu już lat, miał miejsce. A nawet jeśli - na pewno ktoś by go dostrzegł i zgłosił już wcześniej.


    Rozbawiła mnie ta wypowiedź :) Zdziw się bardzo bo są :) Lecz jak pisałem nie są to jakieś wielkie sprawy. Wyobraź sobie, że taki spory framework DevExpress też ma masę bugów i wycieków mimo, że istnieje już hoho czasu. I to rzeczy na bardzo podstawowym poziomie. Tak samo gcc czy masa innego oprogramowania. Z racji oczywistych nie mogę Ci pokazać naocznych dowodów z sesji profilerowych ani innych raportów tego, że .Net ma takie a nie inne błędy. Musisz więc uwierzyć mi na słowo :) Nie chcę też się rozwodzić o szczegółach. W takim wykorzystaniu bibliotek jak robi to autor tematu nie ma to najmniejszego znaczenia. W aplikacjach, które mają po 300MB także :)

    marcinj12 wrote:
    Nigdy nie zdarzyła mi się taka sytuacja, robiłem kiedyś testy z których wynikało, że przy konfiguracji sprzętowej na której program działał, pamięć nigdy nie przekroczyła coś między 50 a 300MB (niestety, dokładnych wyników już nie pamiętam...).


    To zależy co to za aplikacja. Są jeszcze aplikacje, które pobierają dane z bazy danych (spore ilości). Tutaj łatwo można przekroczyć i 1Gb.
  • #10
    marcin w
    Level 22  
    gaskoin wrote:
    checkBox1.Checked = !checkBox1.Checked


    Nie w tym rzecz, jasne że to co piszecie jest prawdą, ale nie chodziło mi o metodę zapisu, w jaki sposób zmieniam wartość ob. checkbox.

    Ten przykład jest tylko dla zobrazowania problemu. Nie jest ważna sama forma zapisu, lecz efekt, o którym piszę.