GoSuda

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

By snowmerak views ...

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.
  • 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:

  1. Tegyük fel, hogy van két memóriaterület, amelyek egymástól körülbelül 32 MB távolságra vannak.
  2. Ebben a két memóriaterületen egyenként 5 objektum van lefoglalva.
    1. A A, B, C objektumok az 1. területen vannak.
    2. A D, E objektumok a 2. területen vannak.
  3. Az objektumok hivatkozási sorrendje A -> D -> B -> E -> C.
  4. Amikor a GC elindul, meglátogatja A-t, majd D-t, B-t, E-t és végül C-t.
  5. 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.
  6. 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.
  7. 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.

Hivatkozások