Poprawne jest stosowanie GC.Collect (); GC.WaitForPendingFinalizers ();?

Zacząłem przeglądać jakiś kod w projekcie i znalazłem coś takiego:

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

Te linie zwykle pojawiają się na metodach, które są pomyślane w celu zniszczenia obiektu pod warunkiem zwiększenia wydajności. Dodałem ten komentarz:

  1. jawne wywołanie garbage collection o zniszczeniu każdego obiektu zmniejsza wydajność, ponieważ nie bierze pod uwagę, czy jest to absolutnie konieczne dla wydajności CLR.
  2. wywołanie tych instrukcji w kolejność ta powoduje, że każdy obiekt jest niszczony tylko wtedy, gdy inne obiekty są finalizowane. Dlatego obiekt, który może zostać zniszczony niezależnie, musi czekać na zniszczenie innego obiektu bez rzeczywistej konieczności.
  3. może wygenerować impas (zobacz: to pytanie )
Czy 1, 2 i 3 są prawdziwe? Czy możesz podać jakieś referencje potwierdzające twoje odpowiedzi?

Chociaż jestem prawie pewien moich uwag, muszę być jasny w moich argumentach, aby wyjaśnić mojemu zespół dlaczego jest to problem . Dlatego proszę o potwierdzenie i referencje.

Author: Community, 2012-09-04

6 answers

Krótka odpowiedź brzmi: wyjmij to. Ten kod prawie Nigdy nie poprawi wydajności, ani długoterminowego wykorzystania pamięci.

Wszystkie twoje punkty są prawdziwe. (It Może wygenerować impas; nie oznacza to, że zawsze będzie.) Wywołanie GC.Collect() zbierze pamięć wszystkich generacji GC. To robi dwie rzeczy.
  • gromadzi wszystkie pokolenia za każdym razem - zamiast tego, co GC będzie robić domyślnie, czyli zbierać tylko generacja, gdy jest pełna. W typowym użyciu Gen0 zbiera (mniej więcej) dziesięć razy częściej niż Gen1, który z kolei zbiera (mniej więcej) dziesięć razy częściej niż Gen2. Ten kod będzie zbierał wszystkie pokolenia za każdym razem. Kolekcja Gen0 jest zazwyczaj Poniżej 100ms; Gen2 może być znacznie dłuższy.
  • [[9]}Promuje przedmioty nie kolekcjonowane do następnej generacji. Oznacza to, że za każdym razem, gdy wymusisz zbiór i nadal masz odniesienie do jakiegoś obiektu, obiekt ten zostanie promowany do kolejne pokolenie. Zazwyczaj zdarza się to stosunkowo rzadko, ale kod taki jak poniżej wymusi to znacznie częściej: {]}
    void SomeMethod()
    { 
     object o1 = new Object();
     object o2 = new Object();
    
     o1.ToString();
     GC.Collect(); // this forces o2 into Gen1, because it's still referenced
     o2.ToString();
    }
    

Bez GC.Collect(), oba te przedmioty zostaną zebrane przy następnej okazji. z zbiorem jako writte, o2skończy się w Gen1 - co oznacza, że zautomatyzowana kolekcja Gen0 nie zwolni tej pamięci.

Warto też zwrócić uwagę na jeszcze większy horror: w trybie debugowania GC działa inaczej i nie odzyskuje żadnej zmiennej, która jest nadal w zakresie (nawet jeśli nie jest używana później w bieżącej metodzie). Tak więc w trybie debugowania powyższy kod nie będzie nawet zbierać o1 podczas wywoływania GC.Collect, więc zarówno o1, jak i o2 będą promowane. Może to prowadzić do bardzo nieregularnego i nieoczekiwanego użycia pamięci podczas debugowania kodu. (Artykuły takie jak to podkreślają to zachowanie.)

EDIT: [41] Po przetestowaniu tego zachowania, trochę prawdziwej ironii: jeśli masz metodę coś w rodzaju to:

void CleanUp(Thing someObject)
{
    someObject.TidyUp();
    someObject = null;
    GC.Collect();
    GC.WaitForPendingFinalizers(); 
}

... wtedy jawnie nie zwolni pamięci jakiegoś obiektu, nawet w trybie RELEASE: wypromuje go do następnej generacji GC.

 24
Author: Dan Puzey,
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
2012-09-04 14:55:36

Jest rzecz, którą można łatwo zrozumieć: uruchamianie GC automatycznie czyści wiele obiektów na run (powiedzmy 10000). Wywołanie go po każdym zniszczeniu czyści około jednego obiektu na run.

Ponieważ GC ma duże obciążenie (musi zatrzymywać i uruchamiać wątki, musi skanować wszystkie obiekty żywe) połączenia wsadowe są wysoce zalecane.

Również, co Dobre może wyjść z sprzątania po każdym obiekcie? Jak może to być bardziej wydajne niż dozowanie?

 8
Author: usr,
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
2012-09-04 14:41:24

Twój punkt numer 3 jest technicznie poprawny, ale może się zdarzyć tylko wtedy, gdy ktoś zablokuje podczas finalizatora.

Nawet bez tego rodzaju połączenia, zamknięcie w finalizatorze jest gorsze niż to, co masz tutaj.

Jest kilka razy, kiedy wywołanie GC.Collect() naprawdę pomaga w wydajności.

Do tej pory robiłem to 2, Może 3 razy w mojej karierze. (A może około 5 lub 6 razy, jeśli włączysz te, w których to zrobiłem, zmierzyłem wyniki, a następnie wyjąłem je ponownie - i to jest coś, co powinieneś zawsze zmierzyć po wykonaniu).

W przypadkach, gdy przechodzisz przez setki lub tysiące megów pamięci w krótkim okresie czasu, a następnie przechodzisz na znacznie mniej intensywne korzystanie z pamięci przez długi okres czasu, jawne gromadzenie może być ogromnym lub nawet istotnym ulepszeniem. To się tu dzieje?

Gdziekolwiek indziej, w najlepszym razie będą wolniej pracować i zużywać więcej pamięci.

 6
Author: Jon Hanna,
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
2012-09-05 00:10:31

Zobacz moją drugą odpowiedź tutaj:

Do GC.Zbierać czy nie?

Dwie rzeczy mogą się zdarzyć, gdy zadzwonisz do GC.Collect () yourself: W końcu spędzasz więcej czasu na robienie kolekcji (ponieważ normalne Kolekcje tła nadal będą się zdarzać oprócz podręcznego GC.Collect ()) i będziesz trzymać się pamięci dłużej (ponieważ wymusiłeś pewne rzeczy na generacjach wyższego rzędu, które nie musiały tam iść). Innymi słowy, używając GC.Collect () to prawie zawsze zły pomysł.

/ Align = "center" bgcolor = "# e0ffe0 " / cesarz Chin / / align = center / Collect () yourself jest wtedy, gdy masz określone informacje o swoim programie, które są trudne do poznania przez Garbage Collector. Kanonicznym przykładem jest długotrwały program z wyraźnymi cyklami pracy i lekkiego obciążenia. Możesz wymusić zbiór pod koniec okresu lekkiego obciążenia, przed zajętym cyklem, aby upewnić się, że zasoby są tak wolne, jak to możliwe dla zajętego cyklu. Ale nawet tutaj, można znaleźć lepiej, zastanawiając się, jak aplikacja jest zbudowany (TJ, zaplanowane zadanie działać lepiej?).
 4
Author: Joel Coehoorn,
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
2017-05-23 12:24:56

Użyłem tego tylko raz: aby wyczyścić pamięć podręczną po stronie serwera dokumentów Crystal Report. Zobacz moją odpowiedź w wyjątku Crystal Reports: maksymalny limit zadań przetwarzania raportów skonfigurowany przez administratora systemu został osiągnięty

WaitForPendingFinalizers był dla mnie szczególnie pomocny, ponieważ czasami obiekty nie były odpowiednio czyszczone. Biorąc pod uwagę stosunkowo powolne wykonanie raportu na stronie internetowej - każde niewielkie opóźnienie GC było znikome, a poprawa zarządzania pamięcią dała mi ogólnie lepszy serwer.

 1
Author: gojimmypi,
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
2017-05-23 12:09:14

Napotkaliśmy podobne problemy do @Grzenio, jednak pracujemy z dużo większymi tablicami 2-wymiarowymi, w kolejności od 1000x1000 do 3000x3000, to jest w webservice.

Dodanie większej ilości pamięci nie zawsze jest właściwą odpowiedzią, musisz zrozumieć swój kod i przypadek użycia. Bez pobierania GC wymagamy 16-32GB pamięci (w zależności od wielkości klienta). Bez niego potrzebowalibyśmy 32 - 64GB pamięci i nawet wtedy nie ma gwarancji, że system nie ucierpi. . NET Śmieciarz nie jest idealny.

Nasz webservice posiada pamięć podręczną w kolejności 5-50 milionów znaków (~80-140 znaków na parę klucz / wartość w zależności od konfiguracji), Dodatkowo przy każdym żądaniu klienta konstruujemy 2 macierze, jedną podwójną, jedną boolean, które następnie przekazywane są do innej usługi w celu wykonania pracy. Dla "macierzy" 1000x1000 (macierz 2-wymiarowa) jest to ~25MB, na żądanie. Boolean powie, które elementy potrzebujemy (na podstawie naszej pamięci podręcznej). Każdy wpis cache reprezentuje jedną "komórkę" w "macierzy".

Wydajność pamięci podręcznej drastycznie spada, gdy serwer ma > 80% wykorzystania pamięci z powodu stronicowania.

Odkryliśmy, że jeśli nie będziemy mieli jawnego GC, garbage collector. NET nigdy nie "oczyści" zmiennych przejściowych, dopóki nie będziemy w zakresie 90-95%, w którym to momencie wydajność pamięci podręcznej Uległa drastycznemu pogorszeniu.

Ponieważ proces down-stream często trwał długo (3-900 sekund), wydajność spadła z kolekcji GC był neglible (3-10 sekund na zbiórkę). Zainicjowaliśmy tę zbiórkę po tym, jak zwróciliśmy już odpowiedź klientowi.

Ostatecznie skonfigurowaliśmy parametry GC, również z. Net 4.6 są dalsze opcje. Oto kod. NET 4.5, którego użyliśmy.

if (sinceLastGC.Minutes > Service.g_GCMinutes)
{
     Service.g_LastGCTime = DateTime.Now;
     var sw = Stopwatch.StartNew();
     long memBefore = System.GC.GetTotalMemory(false);
     context.Response.Flush();
     context.ApplicationInstance.CompleteRequest();
     System.GC.Collect( Service.g_GCGeneration, Service.g_GCForced ? System.GCCollectionMode.Forced : System.GCCollectionMode.Optimized);
     System.GC.WaitForPendingFinalizers();
     long memAfter = System.GC.GetTotalMemory(true);
     var elapsed = sw.ElapsedMilliseconds;
     Log.Info(string.Format("GC starts with {0} bytes, ends with {1} bytes, GC time {2} (ms)", memBefore, memAfter, elapsed));
}

Po przepisaniu do użytku z. Net 4.6 podzieliliśmy zbiór śmieci na 2 kroki-prosty zbiór i zbiór zagęszczający.

    public static RunGC(GCParameters param = null)
    {
        lock (GCLock)
        {
            var theParams = param ?? GCParams;
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);
            GC.Collect(theParams.Generation, theParams.Mode, theParams.Blocking, theParams.Compacting);
            GC.WaitForPendingFinalizers();
            //GC.Collect(); // may need to collect dead objects created by the finalizers
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");

        }
    }

    // https://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
    public static RunCompactingGC()
    {
        lock (CompactingGCLock)
        {
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);

            GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
            GC.Collect();
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"Compacting GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
        }
    }

Nadzieję, że to pomoże komuś innemu, jak my spędziłem dużo czasu na badaniu tego.

 1
Author: Justin,
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
2017-11-06 19:28:47