GoSuda

Timpul liber pentru o ceașcă de ceai verde, confiscat de runtime-ul Go, GreenTea GC

By snowmerak
views ...

De ce apare un nou GC?

De ce GC-ul existent?

GC-ul actual al Go poate fi descris prin următoarele afirmații:

  • concurrent tri-color mark and sweep
    • Utilizează un total de 3 culori (alb, gri, negru) pentru a urmări starea obiectelor.
      • Alb: Obiecte care nu au fost încă vizitate
      • Gri: Obiecte vizitate, dar ale căror obiecte copil nu au fost toate vizitate
      • Negru: Obiecte ale căror obiecte copil au fost toate vizitate
      • După finalizarea parcurgerii, toate obiectele albe sunt colectate.
    • Utilizează un Write Barrier pentru a proteja memoria nou alocată în timpul GC.
      • Timpul necesar pentru a activa/dezactiva Write Barrier-ul reprezintă o parte semnificativă din ceea ce se numește Stop-The-World (în continuare STW).
    • Acest proces este efectuat concurent.
  • No Compaction
    • GC-ul Go nu efectuează compactare.
    • Poate apărea fragmentarea memoriei.
      • Totuși, obiectele mai mici de 32kb utilizează cache-ul per-P al alocatorului de memorie (Allocator) al limbajului Go pentru a minimiza fragmentarea.
  • Non-Generational
    • GC-ul Go nu gestionează obiectele pe generații.
    • Toate obiectele aparțin aceleiași generații.
  • Escape Analysis
    • Go determină dacă un obiect este alocat pe heap sau pe stack prin Escape Analysis.
    • În linii mari, dacă se utilizează pointeri dangling sau interfețe, se poate considera că obiectul este alocat pe heap.

Aspectul crucial

GC-ul Go explorează toate obiectele pornind de la rădăcină și efectuează marcarea în trei culori. Acest proces poate fi descris într-o singură frază ca fiind algoritm de explorare concurentă a grafurilor cu sortare topologică. Totuși, este foarte probabil ca fiecare obiect să existe în zone de memorie diferite. Mai exact,

  1. Să presupunem că există două zone de memorie separate de aproximativ 32MB.
  2. În aceste două zone de memorie sunt alocate câte 5 obiecte.
    1. Obiectele A, B, C sunt în zona 1
    2. Obiectele D, E sunt în zona 2
  3. Ordinea de referință a obiectelor este A -> D -> B -> E -> C.
  4. Când începe GC-ul, acesta vizitează A, apoi D, apoi B, apoi E, și în final C.
  5. În acest moment, deoarece A și D se află în zone de memorie diferite, procesul de vizitare a lui D după A necesită mutarea între zonele de memorie.
  6. În acest proces, apare așa-numitul cost de salt de memorie (memory jump cost), care include mutarea între zonele de memorie și umplerea noilor linii de cache. O parte semnificativă din timpul CPU utilizat de aplicație poate fi alocată acestor operațiuni GC.
  7. Acest cost apare continuu pe toată durata execuției GC.

Deci, care este problema?

Acest comportament al GC este dezavantajos în următoarele cazuri:

  • Când numărul de nuclee este mare și memoria este extinsă
    • GC-ul Go este executat în mod fundamental concurent.
    • Totuși, dacă zona de memorie este largă și obiectele sunt dispersate, multiple nuclee vor explora obiecte simultan în diverse spații de memorie.
    • În acest proces, dacă bus-ul dintre CPU și memorie nu este suficient de mare, lățimea de bandă a memoriei poate deveni un blocaj (bottleneck).
    • De asemenea, din cauza distanței fizice, fiecare explorare va avea o latență relativ mare.
  • Când există multe obiecte mici (fine-grained)
    • Dacă utilizați în Go structuri de tip arbore sau graf cu adâncime mare sau cu mulți copii, GC-ul trebuie să exploreze toate aceste obiecte.
    • Dacă obiectele sunt dispersate în zone de memorie diferite, apare o latență din cauza primului motiv menționat.
    • În plus, dacă există multe obiecte mici, GC-ul trebuie să le exploreze pe toate, ceea ce înseamnă că nucleele CPU vor aloca o capacitate considerabilă GC-ului pe durata execuției acestuia.

Pentru a soluționa aceste probleme, echipa Golang a anunțat GreenTea GC.

Nu mai ai luxul de a bea ceai verde

Ce ne răpește ceaiul verde

S-a considerat că domeniul în care poate fi aplicată cea mai rapidă soluție la GC-ul existent este localitatea memoriei (memory locality). Adică, minimizarea salturilor de memorie prin poziționarea obiectelor cât mai aproape unele de altele. Totuși, nu se poate impune un anumit model (pattern) programatorilor care scriu cod, iar alocarea obiectelor este imprevizibilă în funcție de fluxul de lucru (workflow).

De aceea, metoda aleasă de echipa Golang pare să fie Memory Span.

Ce este Memory Span?

Memory Span este o zonă de memorie relativ mare alocată pentru a plasa obiecte mici. Noul GC numit GreenTea GC efectuează colectarea de gunoi pe aceste Memory Span-uri. Operațiunile detaliate sunt aproape identice cu Tri-Color Mark and Sweep existent.

Funcționarea GreenTea GC

În primul rând, GreenTea GC alocă un Memory Span. După cum s-a menționat anterior, dimensiunea este de 8KiB, o mărime considerabilă. În interiorul acestuia pot fi alocate obiecte cu o dimensiune maximă de 512 bytes. Această dimensiune este aleasă astfel încât să fie puțin probabil ca nodurile din arbori sau grafuri (cum ar fi cele menționate în exemplul anterior) sau structurile obișnuite care scapă pe heap să fie mai mari. Obiectele sub 512 bytes se acumulează în acest Memory Span de fiecare dată când scapă pe heap, iar când Memory Span-ul se umple, se alocă un nou Memory Span.

Când apare un eveniment GC, GreenTea GC pune aceste Memory Span-uri într-o coadă și le inspectează secvențial. În acest proces, GreenTea GC utilizează un mecanism de planificare (scheduling) aproape identic cu modelul GMP existent. Sunt implementate și aspecte precum furtul de sarcini (work stealing). În orice caz, un worker care extrage un Memory Span din coadă inspectează obiectele interne ale Memory Span-ului alocat. În acest proces, Tri-Color Mark and Sweep este utilizat în mod identic.

Ce avantaje aduce acest lucru?

Principala diferență față de GC-ul existent este una singură: unitatea pe care se efectuează GC s-a schimbat de la obiect la Memory Span. Prin urmare, GC-ul obține următoarele avantaje:

  • Localitatea Memoriei: Deoarece obiectele sunt grupate în Memory Span, saltul de memorie este minimizat la explorarea obiectelor. Astfel, cache-ul CPU poate fi utilizat la maximum.
  • Îmbunătățirea Performanței GC: Prin efectuarea GC la nivel de Memory Span, GC-ul funcționează mai eficient în medii multi-core.
  • Optimizarea Alocării Memoriei: Deoarece Memory Span-urile sunt alocate cu o dimensiune fixă, se reduce overhead-ul alocării memoriei pentru obiecte mici și scade probabilitatea de fragmentare. Acest lucru îmbunătățește eficiența alocării și eliberării memoriei.

Pe scurt, acum putem aloca obiecte de sub 512 bytes mai frecvent și cu mai puțină grijă.

Totuși, există și aici anumite scenarii avantajoase:

  • Structuri de tip Arbore/Graf: Când fan-out-ul este mare și nu au loc multe modificări
    • B+ tree, Trie, AST (Abstract Syntax Tree)
    • Structuri de date prietenoase cu cache-ul (cache-friendly)
  • Procesare în Batch: Fluxuri de lucru cu alocare masivă de date mici
    • Obiecte mici multiple create prin parsarea JSON
    • Obiecte DAO (Data Access Object) din seturile de rezultate ale bazelor de date

În aceste cazuri, GreenTea GC poate oferi o performanță mai bună decât GC-ul existent. Totuși, nu se aplică în toate situațiile, și în special când obiectele sunt dispersate în zone de memorie diferite, este încă dificil să ne așteptăm la o îmbunătățire dramatică a performanței.

Prin urmare

Echipa Golang pare să aibă intenția de a continua îmbunătățirea GC pe termen lung. Acest GreenTea GC poate fi văzut ca o schimbare minoră într-un domeniu restrâns și nu înlocuiește GC-ul existent. Totuși, GreenTea GC este un exemplu interesant care ne permite să întrezărim problemele cu care se confruntă sau pe care le anticipează echipa Golang. Deși este o problemă complexă, încercarea de a o rezolva prin adăugarea unui concept relativ simplu a fost, de asemenea, impresionantă. Personal, consider că este un caz remarcabil.

GreenTea GC este o funcționalitate experimentală introdusă începând cu Go 1.25. Poate fi activată utilizând variabila de mediu GOEXPERIMENT=greenteagc. Deoarece această funcționalitate este încă experimentală, este necesară o testare adecvată înainte de a fi utilizată în mediul de producție.

Referințe