A Go futtatókörnyezet által elvett egy csésze zöld tea nyugalma, GreenTea GC

Miért jön most még egy új GC?
Miért a régi GC?
A Go jelenlegi GC-je a következőképpen írható le.
- concurrent tri-color mark and sweep
- Összesen 3 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: meglátogatott, de nem minden gyermekobjektumát látogatta meg
- Fekete: minden gyermekobjektumát meglátogatta
- A bejárás végeztével az összes fehér objektum összegyűjtésre kerül.
- Write Barrier segítségével védi a GC futása közben újonnan létrehozott memóriát.
- A Write Barrier be- és kikapcsolásának ideje a közismert Stop-The-World (a továbbiakban STW) jelentős részét teszi ki.
- Ezt a folyamatot párhuzamosan (konkurensen) hajtja végre.
- Összesen 3 színt (fehér, szürke, fekete) használ az objektumok állapotának nyomon követésére.
- No Compaction
- A Go GC nem végez tömörítést (compaction).
- Memóriatöredezettség léphet fel.
- Ugyanakkor a 32 KB-nál kisebb objektumok esetén a Go memóriakezelő (Allocator) per-P gyorsítótárát használja a töredezettség minimalizálására.
- Non-Generational
- A Go GC nem kezeli az objektumokat generációnként.
- Minden objektum ugyanahhoz a generációhoz tartozik.
- Escape Analysis
- A Go az Escape Analysis segítségével dönti el, hogy egy objektum a heapen vagy a stacken kerül-e lefoglalásra.
- Durván szólva, ha lógó mutatót (dangling pointer) vagy interfészt használunk, az objektum a heapen kerül lefoglalásra.
A lényeg
A Go GC a gyökerektől kiindulva bejárja az összes objektumot, és végrehajtja a háromszínű jelölést (tri-color mark). Ezt a folyamatot egy sorban úgy foglalhatjuk össze, hogy topologikus rendezésű gráf párhuzamos bejárási algoritmus. Az egyes objektumok azonban nagy valószínűséggel különböző memóriaterületeken helyezkednek el. Egyszerűen fogalmazva:
- Tegyük fel, hogy van két memóriaterület, amelyek egymástól körülbelül 32 MB távolságra vannak.
- Ebben a két memóriaterületen egyenként 5 objektum van lefoglalva.
- A A, B, C objektumok az 1. területen vannak.
- A D, E objektumok a 2. területen vannak.
- Az objektumok hivatkozási sorrendje
A -> D -> B -> E -> C. - Amikor a GC elindul, meglátogatja A-t, majd D-t, B-t, E-t és végül C-t.
- Mivel A és D különböző memóriaterületeken található, az A meglátogatásától a D meglátogatásáig a memóriaterületek között kell váltani.
- Ez a folyamat memóriaterület-váltást és új gyorsítótár-sorok betöltését okozza, vagyis ún. memóriaugrási (memory jump) költséget. Az alkalmazás CPU-idejének jelentős része erre a GC-munkára fordítódhat.
- Ez a költség a GC futása alatt folyamatosan fennáll.
Mi a probléma?
A GC ilyen működése a következő esetekben komoly gondot okoz.
- Sok mag és nagy memória esetén
- A Go GC alapértelmezés szerint párhuzamosan fut.
- Nagy memóriaterület és szétszórt objektumok esetén azonban több mag párhuzamosan, különböző memóriaterületeken keresgéli az objektumokat.
- Ha a CPU és a memória közötti busz nem elég széles, a memória sávszélesség szűk keresztmetszetté válhat.
- Emellett a fizikai távolság miatt minden egyes bejárási lépés viszonylag nagy késleltetést okoz.
- Sok apró objektum esetén
- Ha mélyen egymásba ágyazott vagy sok gyermekkel rendelkező fákat vagy gráfokat futtatunk Go-ban, a GC-nek ezeket az objektumokat mind be kell járnia.
- Ha ezek az objektumok különböző memóriaterületeken szóródnak szét, az első okból kifolyólag késleltetés lép fel.
- Továbbá sok apró objektum esetén a GC-nek mindet be kell járnia, így a GC futása alatt a CPU-magok jelentős erőforrást fordítanak a GC-re és végeznek munkát.
Ezen problémák megoldására a Golang csapat bemutatta a GreenTea GC-t.
Nincs többé időd zöld teát inni
Mi rabolja el a zöld teát?
Úgy tűnik, a meglévő GC leggyorsabb megoldásával a memória lokalitás területén lehet a legjobban javítani. Vagyis úgy kell elrendezni az objektumokat, hogy egymáshoz közel helyezkedjenek el, így minimalizálva a memóriaugrásokat. Azonban a programozók kódolási mintáit nem lehet kikényszeríteni, és a munkafolyamattól függően az objektumlefoglalás előre nem jelezhető.
Talán ezért a Golang csapat által választott megoldás a memóriaspan (Memory Span).
Mi az a memóriaspan?
A memóriaspan egy viszonylag nagy memóriaterület, amelyet lefoglalnak, és ebben tárolnak kis objektumokat. A GreenTea GC nevű új GC ezeken a memóriaspanokon végez szemétgyűjtést. A részletes működés szinte megegyezik a meglévő Tri-Color Mark and Sweep működésével.
A GreenTea GC működése
A GreenTea GC először memóriaspant foglal le. Mérete, ahogy korábban leírtuk, egy megfelelő méretű 8 KiB. Ebbe legfeljebb 512 bájt méretű objektumok helyezhetők el. Ez nagyjából az a méret, amelynél a példaként említett fák vagy gráfok csomópontjai, illetve a heapre kerülő tipikus struktúrák mérete nehezen lehet nagyobb. Az 512 bájtnál kisebb objektumok heapre kerüléskor mindig ebbe a memóriaspanba kerülnek, és ha ez megtelik, új memóriaspant foglal le a rendszer.
Amikor GC fut, a GreenTea GC ezeket a memóriaspanokat sorba állítja, és egymás után ellenőrzi. Ennek során a GreenTea GC a meglévő GMP-modellhez szinte hasonló ütemezést használ. Megvalósult a munkalopás (work stealing) is. A sorból kivett memóriaspant feldolgozó worker a neki hozzárendelt memóriaspan belső objektumait vizsgálja. Ebben a folyamatban ugyanazt a Tri-Color Mark and Sweep algoritmust használják.
Mi az előny?
A meglévő GC-hez képest a különbség nagyjából egyetlen dologban rejlik: egy-egy alkalommal a GC futtatásának egysége objektumról memóriaspanra változott. Ennek köszönhetően a GC a következő előnyökkel rendelkezik.
- Memória lokalitás: Mivel az objektumok a memóriaspanon belül csoportosulnak, bejárásukkor minimalizálódnak a memóriaugrások. Vagyis a CPU-gyorsítótárat a lehető legjobban ki lehet használni.
- GC teljesítménynövekedés: Mivel a GC memóriaspan egységekben fut, hatékonyabban működik többmagos környezetben.
- Memóriafoglalás optimalizálása: A memóriaspanok fix méretben foglalódnak, ezért kis objektumok lefoglalásakor csökken a memóriafoglalás többletköltsége, és csökken a töredezettség kialakulásának esélye. Ez növeli a memória felszabadításának hatékonyságát.
Röviden: mostantól gyakrabban és könnyedebb szívvel foglalhatunk le 512 bájtnál kisebb objektumokat.
Ugyanakkor van néhány kedvezőbb helyzet is.
- Fa/gráf szerkezetek: magas kimeneti fokú (fan-out) és ritkán változó struktúrák
- B+ fák, Trie, AST (Abstract Syntax Tree)
- Gyorsítótár-barát adatszerkezetek
- Kötegelt feldolgozás: kis adatok nagy mennyiségű lefoglalását igénylő munkafolyamatok
- JSON-elemzés során keletkező sok kis objektum
- Adatbázis-eredményhalmazok DAO objektumai
Ezekben az esetekben a GreenTea GC jobb teljesítményt nyújthat a meglévő GC-nél. Nem minden esetben alkalmazható azonban, és különösen akkor nem várható drámai teljesítményjavulás, ha az objektumok különböző memóriaterületeken szóródnak szét.
Tehát
A Golang csapat valószínűleg hosszú távon is tovább szeretné fejleszteni a GC-t. A GreenTea GC egy kis területen végrehajtott apró változtatásnak tekinthető, és nem helyettesíti a meglévő GC-t. Ugyanakkor érdekes példa arra, hogy a Golang csapat milyen problémákkal szembesül vagy milyen problémákat vár, és figyelemre méltó volt, hogy viszonylag egyszerű koncepció hozzáadásával próbálják megoldani egy meglepően összetett problémát. Személy szerint érdekes esetnek tartom.
A GreenTea GC a Go 1.25-től bevezetett kísérleti funkció. A GOEXPERIMENT=greenteagc környezeti változó használatával engedélyezhető. Mivel a funkció még kísérleti jellegű, éles környezetben történő használat előtt alapos tesztelésre van szükség.