Teoretyczny Garbage Collector zwalniający duże obszary pamięci

0

Hej wszystkim,

Podczas realizowania pewnego zadania musiałem na krótki czas lokować pamięć dla dużych obiektów. Okazało się, że, w tym konkretnym wypadku (liczył się czas wykonania, a pamięci było w opór) bardziej opłacało się przenosić obiekty (z użyciem std::move) do wektora zaalokowanego na ileś tam tysięcy obiektów i kiedy zadanie zostało zrealizowane, zwalniać każdy obiekt po kolei. Wiem, że samo zadanie wykonuje się szybciej niż niszczenie obiektu po użyciu jednak rozwiązanie nie jest jeszcze całkiem przetestowane.

Zastanawia mnie czy możliwa byłaby implementacja takiego GC, który zanim usunie obiekty, łączy je w większe obszary i zwalnia je kiedy przekroczą określoną wielkość. Czy teoretycznie dałoby to jakiś zysk w prędkości wykonania? Czy tak działają GB w Javie lub C#?

2

Wręcz przeciwnie! :D Aktualny default w Javie to G1 -> garbage first. Konsolidacja obszarów pamięci jest bardzo kosztowna bo wymaga przenoszenia obiektów i blokowania wątków które z nich korzystają.
Dla bardzo dużych obszarów są takie rozwiązania jak ZGC, gdzie operacje GC są "wliczone" w wątki korzystające z danych obiektów. Więc np. "w międzyczasie" jak próbujesz użyc jakiegoś obiektu, GC przenosi go w inny obszar heapu. Dzięki temu tracisz trochę na latency, ale unikasz stop the world kiedy leci oznaczanie żywych obiektów.

To co mówisz ma pewien sens, ale pytanie brzmi: skad wiesz że dany obiekt można już zwolnić? A skoro można go zwolnic to czemu go po prostu nie zwolnisz tylko bawisz się w jakieś przenoszenie go w inne miejsce?

1

Na YT jest wiele prezentacji (w tym po polsku) nt GC dla Javy (dla C# trochę mniej).

Bycie szybszym to jest rzecz względna, np słyszałeś słowa (z dziedziny GC) young/old? Optymalizuje to typowe/częste przypadki wynajmowania pamięci na krótko.
To jest to, co zapamiętałem, jest ciekawostek o wiele więcej, zapraszm na YT

3
bilborrd napisał(a):

Podczas realizowania pewnego zadania musiałem na krótki czas lokować pamięć dla dużych obiektów. Okazało się, że, w tym konkretnym wypadku (liczył się czas wykonania, a pamięci było w opór) bardziej opłacało się przenosić obiekty (z użyciem std::move) do wektora zaalokowanego na ileś tam tysięcy obiektów i kiedy zadanie zostało zrealizowane, zwalniać każdy obiekt po kolei.

To chyba skrót myślowy, bo std::move nic nie przenosi. Rzutuje obiekt na rvalue ref, dzięki czemu można w odpowiednim kontekście wywołać move constructor albo move operator, które z kolei mogą wywołać move constructor i move operator dla pól klasy. W skrócie - efektem powinno być ponowne użycie tej samej dynamicznie zaalokowanej pamięci, zamiast alokacji nowej.
Nie wiem jak wyglądały Twoje dane, ale jeżeli wszytko było polem klasy, to kosztem były małe alokacje. Jeżeli biblioteka standardowa nie posiadała odpowiedniej ilości pamięci od systemu, to co chwila musiała prosić o nowe strony.

(Chyba, że mówisz o move z algorithms, ale efekt będzie taki sam ... )

Wiem, że samo zadanie wykonuje się szybciej niż niszczenie obiektu po użyciu jednak rozwiązanie nie jest jeszcze całkiem przetestowane.

A co rozumiesz przez niszczenie obiektu? Wywołanie destruktora? Twoje "obiekty" robiły coś więcej poza zwalnianiem buforów w destruktorze? W jaki sposób to jest wolne? Oddajesz pamięć do alokatora biblioteki standardowej, a ten sobie porządkuje swoje odcinki pamięci.

Zastanawia mnie czy możliwa byłaby implementacja takiego GC, który zanim usunie obiekty, łączy je w większe obszary i zwalnia je kiedy przekroczą określoną wielkość. Czy teoretycznie dałoby to jakiś zysk w prędkości wykonania? Czy tak działają GB w Javie lub C#?

Obiekty czy pamięć? Pamięć dostajesz od biblioteki standardowej (no chyba, że alokujesz strony manualnie) i ta posiada odpowiednie mechanizm zwalniania i scalania pamięci. Natomiast niszczenie obiektu, to tylko wywołanie funkcji sprzątającej.

Trochę mi się to kojarzy z placement new (a od c++20 także, std::construct_at), które pozwala stworzyć obiekt na wskazanym miejscu w pamięci, ale nic o tym nie wspominasz.

Nawet w C się da. Pracuję nad duża bazą kodu w C i mamy predefiniowane bufory dla obiektów o danym rozmiarze per wątek, a także funkcje, które periodycznie oraz partiami zamiatają niektóre bufory, których czas życia nie do końca jest znany, szukając starych do usunięcia. Ale tylko dlatego, że czas życia jest związany z timerem.
Technik optymalizacji nie brakuje i już parę padło, ale najpierw trzeba poznać istotę problemu. Profiler Twoim najlepszy przyjacielem ; ).

2

Poczytaj w kontekście C++ o:

W szerszym kontekście możesz jeszcze zahaczyć o:

2
Shalom napisał(a):

To co mówisz ma pewien sens, ale pytanie brzmi: skad wiesz że dany obiekt można już zwolnić? A skoro można go zwolnic to czemu go po prostu nie zwolnisz tylko bawisz się w jakieś przenoszenie go w inne miejsce?

generalnie, jakby Kolega @bilborrd zeznał więcej co to jest i jakie niuanse dynamiki, jak duże są duże obszary (ile % cukru w cukrze) itd ... bo było lepiej

1

Jeśli zarządzanie pamięcią dla takiego algorytmu jest dominującym kosztem to pewnie jest pole do poprawy. Użyj jakiegoś profilera np. https://www.jetbrains.com/help/clion/cpu-profiler.html , https://github.com/brendangregg/FlameGraph

1 użytkowników online, w tym zalogowanych: 0, gości: 1