GoSuda

Go futásidejű környezet által elvett, egy csésze zöld tea nyugalma, a GreenTea GC

By snowmerak
views ...

Miért van szükség új GC-re?

A létező GC-k miért

Jelenleg a Go GC-je a következőképpen írható le:

  • concurrent tri-color mark and sweep
    • Három színt (fehér, szürke, fekete) használ az objektumok állapotának nyomon követésére.
      • Fehér: Még nem látogatott objektum
      • Szürke: Látogatott, de még nem az összes gyermekobjektumát látogatta meg
      • Fekete: Az összes gyermekobjektumát meglátogatta
      • A bejárás után az összes fehér objektum begyűjtésre kerül.
    • Write Barrier-t használ a GC során újonnan létrehozott memória védelmére.
      • A Write Barrier be- és kikapcsolásának ideje az, amit gyakran Stop-The-World (a továbbiakban STW) jelentős részének neveznek.
    • Ezt a folyamatot konkurensen hajtja végre.
  • No Compaction
    • A Go GC nem végez tömörítést.
    • Memóriafragmentáció léphet fel.
      • Azonban a 32 kb alatti objektumok a Go nyelv memóriafoglalójának (Allocator) per-P cache-ét használják a fragmentáció minimalizálására.
  • Non-Generational
    • A Go GC nem kezeli az objektumokat generációk szerint.
    • Minden objektum ugyanahhoz a generációhoz tartozik.
  • Escape Analysis
    • A Go az Escape Analysis segítségével dönti el, hogy az objektumok a heap-en vagy a stack-en kerülnek-e allokálásra.
    • Nagyjából, ha dangling pointereket vagy interface-eket használunk, akkor feltételezhető, hogy a heap-en kerülnek allokálásra.

Ami fontos

A Go GC-je az, hogy minden objektumot a gyökértől kezdve bejár, és háromszínű jelölést végez. Ez a folyamat egyetlen sorban leírva egy topológiai rendezésű gráf konkurens bejáró algoritmusa. Azonban az egyes objektumok nagy valószínűséggel különböző memória területeken helyezkednek el. Egyszerűen fogalmazva:

  1. Tegyük fel, hogy két memória terület van, amelyek körülbelül 32 MB távolságra vannak egymástól.
  2. Ezen két memória területen 5 objektum van allokálva.
    1. Az A, B, C objektumok az 1. területen
    2. A D, E objektumok a 2. területen
  3. Az objektumok hivatkozási sorrendje A -> D -> B -> E -> C.
  4. Amikor a GC elindul, meglátogatja az A-t, majd a D-t, majd a B-t, majd az E-t, majd a C-t.
  5. Ekkor, mivel az A és a D különböző memória területeken helyezkednek el, az A látogatását követően a D látogatásához a memória területet váltani kell.
  6. Ez a folyamat úgynevezett memória ugrási költséget von maga után, mint például a memória terület váltása és új cache line-ok feltöltése. Az alkalmazás által felhasznált CPU idő jelentős része ilyen GC műveletekre fordítódhat.
  7. Ez a költség a GC futása alatt folyamatosan fennáll.

Szóval mi a probléma?

Ez a GC-működés a következő esetekben problémás:

  • Sok mag és nagy memória esetén
    • A Go GC alapvetően konkurensen fut.
    • Azonban, ha a memória területe széles és az objektumok elosztottak, több mag konkurensen fog objektumokat keresni különböző memóriaterületeken.
    • Ebben a folyamatban, ha a CPU és a memória közötti busz nem elég nagy, a memória sávszélesség szűk keresztmetszetté válhat.
    • Továbbá, mivel fizikailag távolság van, minden egyes keresés viszonylag nagy késedelmet okoz.
  • Sok apró objektum esetén
    • Ha mély, vagy sok gyermeket tartalmazó fát vagy gráfot üzemeltet Go-ban, a GC-nek az összes objektumot meg kell vizsgálnia.
    • Ebben a folyamatban, ha az objektumok különböző memóriaterületeken oszlanak el, az első ok miatt késleltetés lép fel.
    • Továbbá, ha sok apró objektum van, a GC-nek az összes objektumot meg kell vizsgálnia, ezért a GC futása alatt a CPU magok jelentős kapacitást fognak a GC-re fordítani és dolgozni fognak.

Ezen problémák megoldására a Golang csapat bejelentette a GreenTea GC-t.

Többé nincs ideje teát inni

Ami elveszi a teát

A létező GC-k leggyorsabb megoldásaként a memóriahozzárendelést azonosították. Vagyis az objektumok egymáshoz közel helyezkednek el, minimalizálva a memória ugrásokat. Azonban a kódot író programozó mintái nem kényszeríthetők, és az objektumok allokálása a munkafolyamattól függően kiszámíthatatlan.

Ezért a Golang csapat a memória span-t (Memory Span) választotta.

Mi az a Memory Span?

A memória span egy viszonylag nagy memóriaterületet foglal le, és azon belül allokál kis objektumokat. Az új, GreenTea GC-nek nevezett GC pedig ezen memória span-eken végez szemétgyűjtést. A részletes működés szinte teljesen megegyezik a meglévő Tri-Color Mark and Sweep eljárással.

A GreenTea GC működése

Először is, a GreenTea GC allokál egy memória span-t. Ahogy korábban említettük, a mérete viszonylag nagy, 8 KiB. Ezen belül legfeljebb 512 bájt méretű objektumokat lehet allokálni. Ez éppen akkora méret, mint a példaként említett fa vagy gráf csomópontjainak mérete, vagy egy általános heap-re szökő struktúra mérete, ami ennél nagyobb aligha lehet. Az 512 bájt alatti objektumok minden egyes heap-re szökéskor ebbe a memória span-be kerülnek, és amikor ez a memória span megtelik, egy új memória span kerül allokálásra.

Amikor GC történik, a GreenTea GC sorba állítja ezeket a memória span-eket, és szekvenciálisan ellenőrzi őket. Ebben a folyamatban a GreenTea GC szinte azonos ütemezést használ, mint a meglévő GMP modell. A munka lopása (work stealing) is megvalósításra került. Mindenesetre, a sorból egy memória span-t kiváltó worker ellenőrzi a hozzá rendelt memória span belső objektumait. Ebben a folyamatban a Tri-Color Mark and Sweep azonos módon kerül felhasználásra.

Milyen előnyökkel jár ez?

Ebben a folyamatban a meglévő GC-től való különbség lényegében egyetlen dologban rejlik. A GC végrehajtásának egysége objektumról memória span-re változott. Ennek eredményeként a GC a következő előnyökkel jár:

  • Memóriahozzárendelés: Mivel az objektumok a memória span-ben gyűlnek össze, az objektumok keresésekor a memória ugrások minimalizálódnak. Ez azt jelenti, hogy a CPU cache-ét a lehető legjobban kihasználhatja.
  • GC teljesítmény javulása: A GC memória span egységenkénti végrehajtásával a GC hatékonyabban működik többmagos környezetben.
  • Memóriaallokáció optimalizálása: Mivel a memória span-ek fix méretben kerülnek allokálásra, a kis objektumok allokálásakor csökken a memóriaallokáció overhead-je, és csökken a fragmentáció valószínűsége. Ez növeli a memória deallokáció hatékonyságát.

Röviden, mostantól gyakrabban és könnyebb szívvel allokálhatunk 512 bájt alatti objektumokat.

Azonban itt is vannak bizonyos előnyös esetek.

  • Fa/gráf struktúrák: nagy fan-out és ritka változások esetén
    • B+ fa, Trie, AST (Abstract Syntax Tree)
    • Cache-barát adatstruktúrák
  • Kötegelt feldolgozás: kis adatok tömeges allokációs munkafolyamata
    • JSON-elemzésből létrejövő számos apró objektum
    • Adatbázis eredményhalmaz DAO objektumai

Ezekben az esetekben a GreenTea GC jobb teljesítményt nyújthat, mint a meglévő GC. Azonban nem minden esetben alkalmazható, és különösen akkor, ha az objektumok különböző memóriaterületeken oszlanak el, továbbra is nehéz drámai teljesítménynövekedést várni.

Összefoglalva

A Golang csapat valószínűleg hosszú távon is fejleszteni kívánja a GC-t. Ez a GreenTea GC egy kisebb változtatásnak tekinthető egy kis területen, és nem helyettesíti a meglévő GC-t. Azonban a GreenTea GC érdekes példa arra, hogy a Golang csapat milyen problémákkal szembesül, vagy milyen problémákat vár. Meglepően összetett probléma, de a viszonylag egyszerű koncepció hozzáadása révén történő megoldási kísérlet is lenyűgöző volt. Személy szerint érdekes esetnek tartom.

A GreenTea GC egy kísérleti funkció, amelyet a Go 1.25-től vezetnek be. Az GOEXPERIMENT=greenteagc környezeti változó használatával aktiválható és használható. Ez a funkció még kísérleti, ezért alapos tesztelésre van szükség, mielőtt éles környezetben használnák.

Referenciák