Garbage collection (GC) w Go to automatyczny mechanizm zarządzania pamięcią, który po raz pierwszy pojawił się w wczesnych wersjach języka. Historycznie GC w Go był jednym z źródeł krytyki ze względu na wpływ na wydajność. Jednak wraz z rozwojem języka, szczególnie po wersji Go 1.5, został znacznie poprawiony: obecnie stosuje się potrójny współbieżny (concurrent, tricolor, mark-and-sweep) mechanizm zbierania śmieci z minimalnymi pauzami (low-pause GC).
Problem pojawia się, gdy programy tworzą dużą liczbę tymczasowych obiektów lub gdy nie usuwają odniesień do nieużywanych struktur: zwiększa to obciążenie GC i może prowadzić do długich pauz. Szczególną uwagę należy zwrócić na rodzaje obiektów, cykliczne odwołania i długie łańcuchy odwołań leżące poza stosem.
Rozwiązanie — monitorować przydział pamięci, korzystać z profilowania i dostosowywać GC za pomocą zmiennej środowiskowej GOGC, minimalizując liczbę alokacji w wewnętrznych pętlach i krytycznych sekcjach. Warto pamiętać, że zbieranie śmieci w Go działa tylko dla sterty (heap): wszystko, co jest przydzielane na stosie, zostanie automatycznie usunięte po wyjściu z zakresu widoczności, a obiekty, które „odeszły” do sterty, są kontrolowane przez GC.
Przykład kodu:
// Profilowanie allloc i optymalizacja GC import ( "runtime" "fmt" ) func main() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) fmt.Printf("Przed alokacją: %d bytes ", memStats.Alloc) s := make([]int, 1_000_000) for i := range s { s[i] = i } runtime.GC() // ręczne oczyszczanie runtime.ReadMemStats(&memStats) fmt.Printf("Po GC: %d bytes ", memStats.Alloc) }
Kluczowe cechy:
GOGC (na przykład GOGC=100 — standard; zmniejszenie przyspiesza GC, ale zwiększa zużycie CPU).Jak dowiedzieć się, który obiekt „odchodzi” do sterty, a który pozostaje na stosie?
Odpowiedź: W tym celu używa się analizy ujścia (escape analysis), którą można analizować za pomocą flagi kompilatora go build -gcflags="-m". Obiekty, które są zwracane na zewnątrz z funkcji lub używane w zamknięciach, najczęściej idą do sterty.
Przykład kodu:
func escape() *int { v := 42 return &v // v będzie w stercie }
Czy GC wpływa na wszystkie zmienne, w tym te, które znajdują się na stosie?
Nie, GC działa tylko z stertą (obiekty przydzielone na stercie). Wszystko, co jest przydzielone na stosie, jest automatycznie czyszczone po zakończeniu funkcji.
Czy ręczne wywołanie runtime.GC() może znacznie poprawić wydajność?
Wręcz przeciwnie, ręczne wywołanie często pogarsza wydajność, zwiększając zużycie CPU. Należy go używać tylko do testów lub w przypadkach, które wymagają debugowania.
Negatywny przypadek
Programista pisze serwis, który w każdym zapytaniu tworzy nowe duże slice, nie myśląc o ich żywotności. Szybko prowadzi to do wzrostu obciążenia GC, nagłych pauz i spadku wydajności.
Plusy:
Minusy:
Pozytywny przypadek
Programista optymalizuje serwis, profiluje alokacje, ponownie wykorzystuje bufory przez sync.Pool, zmniejsza częstotliwość wywołań GC i minimalizuje przydział pamięci w gorących miejscach.
Plusy:
Minusy: