GoSuda

Go Runtime отнема спокойствието на чаша зелен чай, GreenTea GC

By snowmerak
views ...

Защо се появява нов GC?

Защо съществуващият GC?

Текущият GC на Go може да бъде описан със следните термини:

  • concurrent tri-color mark and sweep
    • Използват се три цвята (бял, сив, черен) за проследяване на състоянието на обектите.
      • Бял: Обект, който все още не е посетен
      • Сив: Обект, който е посетен, но не всичките му дъщерни обекти са посетени
      • Черен: Обект, чиито всички дъщерни обекти са посетени
      • След завършване на обхода, всички бели обекти се събират.
    • Използва се Write Barrier за защита на новосъздадената памет по време на GC.
      • Времето за включване/изключване на Write Barrier представлява значителна част от т.нар. Stop-The-World (по-нататък STW).
    • Този процес се изпълнява едновременно.
  • No Compaction
    • GC на Go не извършва компактиране.
    • Може да възникне фрагментация на паметта.
      • Въпреки това, обекти с размер под 32kb минимизират фрагментацията, като използват per-P кеша на Go Allocator.
  • Non-Generational
    • GC на Go не управлява обекти по поколения.
    • Всички обекти принадлежат към едно и също поколение.
  • Escape Analysis
    • Go използва Escape Analysis, за да определи дали даден обект да бъде разпределен в Heap или в Stack.
    • Грубо казано, ако се използват dangling pointers или интерфейси, обектът може да се счита за разпределен в Heap.

Важното е

GC на Go търси всички обекти, започвайки от корена, и извършва трицветно маркиране. Този процес може да бъде описан с едно изречение като "алгоритъм за едновременно търсене на топологично сортиран граф". Въпреки това, има голяма вероятност отделните обекти да се намират в различни области на паметта. Казано по-просто:

  1. Да предположим, че има две области на паметта, отдалечени една от друга на около 32 MB.
  2. Във всяка от тези две области на паметта са разпределени по 5 обекта.
    1. Обекти A, B, C са в област 1.
    2. Обекти D, E са в област 2.
  3. Редът на препращане на обектите е A -> D -> B -> E -> C.
  4. Когато GC започне, той посещава A, след това D, след това B, след това E, и накрая C.
  5. В този момент, тъй като A и D се намират в различни области на паметта, при преминаването от A към D е необходимо преместване между областите на паметта.
  6. Този процес включва т.нар. "скок на паметта", включващ преместване между области на паметта и запълване на нови кеш линии. Значителна част от времето на процесора, използвано от приложението, може да бъде разпределена за такива GC операции.
  7. Тази цена възниква непрекъснато по време на изпълнението на GC.

И така, какъв е проблемът?

Такова поведение на GC е пагубно в следните случаи:

  • Когато има голям брой ядра и голяма памет
    • GC на Go по принцип се изпълнява едновременно.
    • Въпреки това, ако областта на паметта е голяма и обектите са разпръснати, множество ядра ще търсят обекти едновременно в различни паметови пространства.
    • В този процес, ако шината между CPU и паметта не е достатъчно голяма, пропускателната способност на паметта може да се превърне в тесно място.
    • Освен това, тъй като съществува физическо разстояние, всяко търсене ще бъде сравнително голямо забавяне.
  • Когато има много малки обекти
    • Ако оперирате с дърво или граф с голяма дълбочина или много дъщерни елементи в Go, GC трябва да претърси всички тези обекти.
    • В този процес, ако обектите са разпръснати в различни области на паметта, възниква забавяне поради първата причина.
    • Освен това, ако има много малки обекти, GC трябва да ги претърси всички, така че по време на изпълнението на GC, CPU ядрото ще разпредели значителна част от ресурсите си за GC и ще работи.

За да реши тези проблеми, екипът на Golang представи GreenTea GC.

Вече не можете да си позволите повече зелен чай.

Какво отнема зеления чай?

Като най-бързо решение за съществуващия GC, областта, която се разглежда, е локалността на паметта. Тоест, минимизиране на скоковете в паметта, като обектите се позиционират близо един до друг. Въпреки това, не може да се наложи моделът на програмиста, който пише кода, а разпределението на обектите е непредсказуемо в зависимост от работния процес.

Ето защо методът, избран от екипа на Golang, е Memory Span.

Какво е Memory Span?

Memory Span е място, където се разпределя сравнително голяма област от паметта, за да се разпределят малки обекти. Новият GC, наречен GreenTea GC, извършва събиране на отпадъци за този Memory Span. Подробното поведение е почти идентично със съществуващия Tri-Color Mark and Sweep.

Работа на GreenTea GC

Първо, GreenTea GC разпределя Memory Span. Размерът, както беше споменато по-рано, е 8KiB, което е сравнително голям размер. В него могат да бъдат разпределени обекти с максимален размер от 512 bytes. Това е приблизително размерът на възлите на дърво или граф, които бяха дадени като пример, или размерът на общи структури, които "избягват" към Heap, който е трудно да бъде по-голям от това. Обекти под 512 bytes се натрупват в този Memory Span всеки път, когато "избягват" към Heap, и когато този Memory Span се запълни, се разпределя нов Memory Span.

Сега, когато възникне GC, GreenTea GC поставя тези Memory Spans в опашка и ги проверява последователно. В този процес GreenTea GC използва почти идентично планиране с модела GMP. Има и области като открадване на задачи. Във всеки случай, работник, който изважда Memory Span от опашката, проверява вътрешните обекти на своя разпределен Memory Span. В този процес Tri-Color Mark and Sweep се използва по същия начин.

Какви са предимствата?

Основната разлика между този процес и съществуващия GC е само една. Единицата, в която се извършва GC, се промени от обект на Memory Span. В резултат на това GC има следните предимства:

  • Локалност на паметта: Тъй като обектите са събрани в Memory Span, скоковете в паметта се минимизират при търсене на обекти. Тоест, кешът на CPU може да бъде използван максимално.
  • Подобрена производителност на GC: Извършването на GC на ниво Memory Span позволява на GC да работи по-ефективно в многоядрена среда.
  • Оптимизация на разпределението на паметта: Тъй като Memory Spans се разпределят с фиксиран размер, овърхедът при разпределение на малки обекти намалява и вероятността от фрагментация намалява. Това повишава ефективността на освобождаване на паметта.

Накратко, сега можем да разпределяме обекти под 512 bytes по-често и с по-малко притеснения.

Въпреки това, и тук има някои благоприятни сценарии.

  • Структури от дърво/граф: когато fan-out е висок и промените са редки.
    • B+ дърво, Trie, AST (Abstract Syntax Tree)
    • Кеш-приятелски структури от данни
  • Пакетна обработка: работен процес за масово разпределение на малки данни.
    • Множество малки обекти, генерирани от JSON парсинг.
    • DAO обекти от набори резултати от база данни.

В тези случаи GreenTea GC може да покаже по-добра производителност от съществуващия GC. Въпреки това, той не е приложим във всички случаи и особено когато обектите са разпръснати в различни области на паметта, все още е трудно да се очаква драматично подобрение на производителността.

И така

Екипът на Golang изглежда има намерение да продължи да подобрява GC в дългосрочен план. Този GreenTea GC може да се разглежда като малка промяна в малка област и не замества съществуващия GC. Въпреки това, GreenTea GC изглежда е интересен случай, който дава представа за проблемите, пред които е изправен или очаква екипът на Golang. Въпреки че е изненадващо сложен проблем, опитът да се реши с добавяне на сравнително прости концепции също е впечатляващ. Лично аз смятам, че това е интересен случай.

GreenTea GC е експериментална функция, въведена от Go 1.25. Може да се активира и използва чрез променливата на средата GOEXPERIMENT=greenteagc. Тъй като тази функция все още е експериментална, са необходими достатъчно тестове, преди да се използва в производствена среда.

Референции