C# – Killing the system!

C# – Killing the system!

C# i środowisko uruchomieniowe języka (common language runtime) są ściśle ze sobą powiązane. I Jednym z przykładów tej relacji jest sposób działania czyszczenia pamięci. Wiemy, że w C# możemy zbudować nowy obiekt, kiedy tylko będziemy go potrzebować. Używając operatora new, możemy stworzyć obiekt.

I nigdy nie musimy martwić się o zarządzanie czasem życia tego obiektu. Środowisko wykonawcze ma wystarczającą ilość informacji, aby określić, kiedy już nie używamy obiektu, i może zwolnić miejsce tego obiektu.

I moduł odśmiecania pamięci (garbage collector) robi to okresowo sprawdza on wszystkie statyczne zmienne globalne, które są dostępne w naszym programie, a także wszystkie zmienne lokalne działające w naszym programie.   

Person person = new Person();

public class Person
{
    public int Id { get; set; } 
    public string Name { get; set; }
    public string Email { get; set; }
}

Robiąc to, znajdzie na przykład zmienną o nazwie osoba, która odwołuje się do obiektu Osoba na stercie. I Wie też, że ten obiekt osoba ma właściwość string, która odwołuje się do innego obiektu na stercie. Więc wie, że te bloki są używane, ale może oznaczyć te bloki jako wolne i otwarte, ponieważ nie znalazł już żadnych odniesień do tych obszarów. Mogą to być zmienne, które wyszły poza zakres.

I Kompilator C# posiada wystarczające informacje o strukturach danych, których używamy, a moduł odśmiecania pamięci zna dokładny rozmiar wszystkich tych obiektów i wie, jakie inne odwołania mogą zawierać. I Teraz, gdy moduł czyszczenia pamięci znajdzie te nieużywane miejsca, może je zwolnić, ale tutaj wykonuje jeszcze jeden dodatkowy krok.

Robi również to, co nazywamy zagęszczaniem sterty. Więc te bloki, które mogą znajdować się między przydzielonymi obiektami można skondensować, abyśmy mieli dostępne większe bloki wolnej przestrzeni.

Dzięki temu sterta jest w dobrej kondycji i jest to bardzo podobna do defragmentacji dysku twardego. I Kompaktowanie sterty jest w dużej mierze optymalizacją, która może pomóc naszym programom działać szybciej!!!

Ponieważ moduł czyszczenia pamięci przesuwa obiekty, musi również naprawić odniesienia. Zatem właściwość nazwa osoby, która poprzednio odwoływała się do ciągu znaków znajdującego się gdzies na stertcie, to to odwołanie musi zostać naprawione przez moduł czyszczenia pamięci, tak aby odwoływało się do właściwego obiektu.

Ale My nie musimy się tym martwić. To wszystko dzieje się za kulisami i jest to jedna z zalet uruchamiania kodu zarządzanego. MY Możemy po prostu skupić się na rozwiązywaniu jakiegoś problemu biznesowego i pozwolić modułowi czyszczenia pamięci zająć się wszystkimi problemami związanymi z pamięcią. W pewnym momencie możemy już nie potrzebować tego obiektu osoba, bo wyjdzie poza zakres.
Nie będzie już odniesienia do tego obiektu na stercie.

A moduł czyszczenia pamięcigarbage collector może przyjść i zwolnić wszystko, co było związane z tą zmienną. I teraz Chcę zademonstrować niektóre funkcje modułu czyszczenia pamięci (garbage collectora) za pomocą prostej aplikacji konsolowej.

class Program
{
 static void Main(string[] args)
 {
   for (int i = 0; i < 1000000; i++)
   {
    new Person() { Id = i, Name = i.ToString(), Email = i.ToString() };

    if (i % 100000 == 0)
    {
      Console.WriteLine(GetTotalCollections());
    }
   }
 }

 static int GetTotalCollections()
 {
   return GC.CollectionCount(0) +
          GC.CollectionCount(1) +
          GC.CollectionCount(2);
 }

 static List<Person> people = new List<Person>();
}

I co tutaj robimy: 

W pętli tworzymy 1 000 000 obiektów osoba. I nie zamierzamy zapisywać odniesienia do tych obiektów. Po prostu tworzymy ich instancję. I natychmiast staną one się śmieciami na stercie, ponieważ nikt nie ma odniesienia do tych obiektów. Są to bardzo małe obiekty klasy Person co widać powyżej (Id, Name, Email).

I następnie tutaj Co 100 000 przejscie petli będziemy wypisywać całkowitą liczbę operacji czyszczenia pamięci, które miały miejsce.

Uruchommy teraz ten program!

Jak widać, w tej chwili mniej więcej po 200 000 obiektów utworzonych, zanim moduł odśmiecania pamięcigarbage collector włączy się i trochę wyczyścił stertę. Ale spróbujmy czegoś innego.

Wróćmy do naszego programu i powiększmy nasz obiekt osoba. Dodamy tablicę bajtów, która rezerwuje wystarczająco dużo miejsca do przechowywania 100 000 bajtów.         

byte[] table = new byte[100000];

Każdy osoba, którą teraz stworzymy, będzie wymagał znacznie więcej miejsca na stercie.

A teraz ponownie uruchom nasz program!

Jak widać, nadal wykonujemy prace dość szybko, ale tym razem mieliśmy ponad 84375 kolekcji (czyli operacji czyszczenia pamięci) które miały miejsce. Więc garbage collector nie hamował znacząco naszej wydajności, a mimo to ogromnie nam pomagał.

W tej chwili nie przechowujemy odniesienia do żadnego z tych obiektów, ale tak się składa, że mamy tutaj listę i zamiast po prostu tworzyć śmieci, zapiszmy odniesienie do każdego obiektu osoba podczas jego tworzenia na liscie.

people.Add(new Person() { Id = i, Name = i.ToString(), Email = i.ToString() });

A teraz ponownie uruchommy nasz program.

I widzimy, że przeszliśmy przez około 600 000 iteracji, zanim wystąpił wyjątek braku pamięci. Zapełniliśmy całą stertę tak wieloma tablicami bajtowymi i obiektami osoba,
że po prostu zabrakło miejsca, a środowisko wykonawcze nie mogło już przydzielić pamięci.

Wróćmy do programu i przyjrzyjmy się, jak obliczaliśmy całkowitą liczbę kolekcji (czyli operacji czyszczenia pamięci). Używamy klasy o nazwie GC, która jest skrótem od garbage collector, i która udostępnia szereg statycznych metod i statycznych właściwości, których można użyć do interakcji z garbage collector.

Ale musimy być ostrożni, ponieważ niektóre interfejsy API, takie jak metoda Collect, która mówi modułowi czyszczenia pamięci, aby dokonał kolekcji (czyli operacji czyszczenia pamięci) może czasami być szkodliwa dla naszego programu.

Czasami najlepiej jest pozwolić, aby środowisko wykonawcze i moduł czyszczenia pamięci pracowały razem, aby dowiedzieć się, kiedy najlepiej jest uruchomić kolekcję (czyli operacji czyszczenia pamięci) zamiast na siłę i robić to ręcznie.

Metody, których tutaj używamy to CollectionCount(), pytamy moduł czyszczenia pamięci, ile kolekcji zostało uruchomionych w określonej generacji?

Tak w normalnie zarządzanej stercie istnieją trzy generacje obiektów. Kiedy po raz pierwszy tworzymy obiekt, jest on umieszczany w Generacji 0.

A jeśli przeżyją oczyszczanie śmieci, to znaczy nadal będą do niego odwołania po uruchomieniu modułu czyszczenia pamięci, zostanie awansowany do Generacji 1, a jeśli przetrwa tam to zostanie awansowany do generacji 2.  

Jest to optymalizacja wykorzystania modułu czyszczenia pamięci ponieważ większość aplikacji ma ogromną liczbę krótkotrwałych obiektów. Tworzymy obiekty, które są używane tylko w jednej metodzie, których później już nie potrzebujemy.

Te typy obiektów będą żyć w generacji 0, która zwykle jest zbierana częściej niż generacja 1 i 2, ponieważ generacja 1 i 2 zawiera obiekty, które już przeżyły kolekcję (czyli operacji czyszczenia pamięci) i mogą zostać przez jakiś czas.

https://github.com/mariuszjurczenko/Csharp

Pobawmy się teraz jeszcze trochę: Cały materiał na tym filmie.

1 comment

  1. Good way of telling, and good piece of writing to take facts on the topic of my presentation subject matter, which i am going to convey in university. Dacia Clemente Marva

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *