Go-körtidens GreenTea GC: En kopp grönt te som tas ifrån dig
Varför ännu en ny GC?
Varför den befintliga GC?
Go:s nuvarande GC kan beskrivas med följande termer:
- concurrent tri-color mark and sweep
- Använder tre färger (vit, grå, svart) för att spåra objektens tillstånd.
- Vit: Objekt som ännu inte har besökts.
- Grå: Objekt som har besökts men vars alla underordnade objekt inte har besökts.
- Svart: Objekt vars alla underordnade objekt har besökts.
- När iterationen är klar samlas alla vita objekt in.
- Använder Write Barrier för att skydda nyallokerat minne under GC.
- Tiden det tar att slå på/av Write Barrier utgör en betydande del av det som kallas Stop-The-World (hädanefter STW).
- Utför denna process samtidigt.
- Använder tre färger (vit, grå, svart) för att spåra objektens tillstånd.
- No Compaction
- Go:s GC utför ingen komprimering.
- Minnesfragmentering kan uppstå.
- Objekt mindre än 32 KB minimerar dock fragmentering genom att använda Go-språkets Allocators per-P-cache.
- Non-Generational
- Go:s GC hanterar inte objekt generationsvis.
- Alla objekt tillhör samma generation.
- Escape Analysis
- Go bestämmer om ett objekt ska allokeras på heapen eller stacken genom Escape Analysis.
- Grovt sett, om man använder dangling pointers eller interfaces, kan det betraktas som allokerat på heapen.
Det viktiga är
Go:s GC genomsöker alla objekt från roten och utför en trefärgad markering. Denna process kan sammanfattas som en "samtidig grafsökalgoritm för topologisk sortering". Dock är sannolikheten stor att varje objekt finns i olika minnesområden. För att uttrycka det enkelt:
- Antag att det finns minnesområden som ligger cirka 32 MB från varandra.
- I dessa två minnesområden är 5 objekt allokerade i varje.
- Objekt A, B, C i område 1
- Objekt D, E i område 2
- Objektens referensordning är
A -> D -> B -> E -> C
. - När GC startar, besöker den A, sedan D, sedan B, sedan E, och slutligen C.
- I detta fall, eftersom A och D finns i olika minnesområden, måste minnesområdet flyttas under processen att besöka A och sedan D.
- Under denna process uppstår en så kallad "minneshoppskostnad", som involverar att flytta minnesområden och fylla nya cachelinjer. En betydande del av den CPU-tid som applikationen använder kan tilldelas sådana GC-operationer.
- Denna kostnad uppstår kontinuerligt under GC-processen.
Så vad är problemet?
Denna GC-beteende är särskilt ogynnsamt i följande fall:
- När antalet kärnor är högt och minnet är stort
- Go:s GC utförs i grunden samtidigt.
- Men om minnesområdet är stort och objekten är spridda, kommer flera kärnor samtidigt att söka efter objekt i olika minnesutrymmen.
- I denna process, om bussen mellan CPU och minne inte är tillräckligt stor, kan minnesbandbredden bli en flaskhals.
- Dessutom, på grund av den fysiska avståndet, kommer varje sökning att medföra en relativt stor fördröjning.
- När det finns många små objekt
- Om ni driver ett träd eller en graf med djup eller många barn i Go, måste GC genomsöka alla dessa objekt.
- Om objekten är spridda över olika minnesområden i denna process, uppstår en fördröjning på grund av den första anledningen.
- Dessutom, om det finns många små objekt, måste GC genomsöka alla dessa objekt, vilket innebär att CPU-kärnan kommer att allokera en betydande mängd resurser till GC under dess utförande.
För att lösa dessa problem har Golang-teamet presenterat GreenTea GC.
Du har inte råd med mer grönt te
Vad berövar dig ditt gröna te?
Det område som ansågs ha den snabbaste lösningen för den befintliga GC var minneslokalitet. Det vill säga, att minimera minneshopp genom att placera objekt nära varandra. Men det är inte möjligt att tvinga programmerarens kodmönster, och objektallokering kan inte förutsägas baserat på arbetsflödet.
Därför har Golang-teamet valt metoden Memory Span.
Vad är Memory Span?
En Memory Span är ett relativt stort minnesområde som allokeras för att allokera små objekt. Och den nya GC, kallad GreenTea GC, utför skräpsamling på denna Memory Span. Den detaljerade funktionen är nästan identisk med den befintliga Tri-Color Mark and Sweep.
GreenTea GC:s funktion
Först allokerar GreenTea GC en Memory Span. Storleken är, som tidigare nämnts, 8 KiB, vilket är en viss storlek. Inom denna kan objekt med en maximal storlek på 512 bytes allokeras. Detta är ungefär den storlek som är svår att överstiga för nodstorleken för det träd eller den graf som nämndes som exempel, eller storleken på en typisk struktur som hamnar på heapen. Objekt under 512 bytes ackumuleras i denna Memory Span varje gång de "flyr" till heapen, och när denna Memory Span är full allokeras en ny Memory Span.
När GC inträffar, lägger GreenTea GC dessa Memory Spans i en kö och inspekterar dem sekventiellt. Under denna process använder GreenTea GC en schemaläggning som är nästan identisk med den befintliga GMP-modellen. Områden som arbetsstöld är också implementerade. Hur som helst, en Worker som tar ut en Memory Span från kön inspekterar de interna objekten i den Memory Span som tilldelats den. Under denna process används Tri-Color Mark and Sweep på samma sätt.
Vilka är fördelarna?
Skillnaden mellan denna process och den befintliga GC är i stort sett bara en. Enheten för GC-utförande har ändrats från objekt till Memory Span. Detta ger GC följande fördelar:
- Minneslokalitet: Eftersom objekt är samlade i Memory Spans minimeras minneshopp vid sökning efter objekt. Detta innebär att CPU-cachen kan utnyttjas maximalt.
- Förbättrad GC-prestanda: Genom att utföra GC på Memory Span-nivå fungerar GC mer effektivt i en miljö med flera kärnor.
- Minnesallokeringsoptimering: Eftersom Memory Spans allokeras med fast storlek minskar overheaden för minnesallokering vid allokering av små objekt, och risken för fragmentering minskar. Detta ökar effektiviteten vid minnesfrigöring.
Kort sagt, vi kan nu allokera objekt under 512 bytes oftare och med större lätthet.
Det finns dock vissa fördelaktiga scenarier även här:
- Träd-/grafstrukturer: När fan-out är hög och ändringar sällan sker.
- B+-träd, Trie, AST (Abstract Syntax Tree)
- Cachevänliga datastrukturer
- Batchbearbetning: Arbetsflöden med massiv allokering av små data.
- Många små objekt som skapas vid JSON-parsning.
- DAO-objekt från databasens resultatuppsättningar.
I dessa fall kan GreenTea GC uppvisa bättre prestanda än den befintliga GC. Det gäller dock inte i alla fall, och särskilt när objekt är spridda över olika minnesområden är det fortfarande svårt att förvänta sig dramatiska prestandaförbättringar.
Slutsats
Golang-teamet verkar ha för avsikt att fortsätta förbättra GC på lång sikt. Denna GreenTea GC kan ses som en mindre förändring inom ett litet område och ersätter inte den befintliga GC. Men GreenTea GC verkar vara ett intressant exempel som ger en inblick i de problem som Golang-teamet står inför eller förväntar sig. Även om det är ett oväntat komplext problem, var försöket att lösa det med tillägg av relativt enkla koncept också imponerande. Personligen tycker jag att det är ett intressant fall.
GreenTea GC är en experimentell funktion som introduceras med Go 1.25. Den kan aktiveras genom att använda miljövariabeln GOEXPERIMENT=greenteagc
. Denna funktion är fortfarande experimentell, så tillräcklig testning krävs innan den används i produktionsmiljöer.