Go и generational GC

Go и generational GC

GolangRu

Тем временем у нас в Go походу планируется Generational GC. Пока только в концепции, и вполне возможно, что в итоге он никогда реализован, не будет, но сам тренд интересен.


Для тех, кто не в курсе - сейчас Go использует Concurrent Mark & Sweep (CMS) сборщик мусора, который обладает как преимуществами, так и недостатками. Преимущества — простота реализации, отсутствие write barrier. Недостатки — потенциальная фрагментация, необходимость обходить весь граф в цикле сборки мусора. Обе эти проблемы решены в определенной степени в Go Runtime, но и сами решения, скажем так, имеют особенности.


Generational GC традиционно ассоциируется с Java — суть в том, что для сборщика мусора существует «два поколения»: молодое и старое. Идея состоит в том, что программа обычно генерирует большое число короткоживущих объектов — сканировать и, по возможности удалять, их имеет смысл часто. В тоже самое время, есть объекты, которые существуют относительно долго (включая те, которые живут в течении всей жизни приложения) — их имеет смысл сканировать относительно редко.


Появляется вопрос — а зачем вообще это нужно? Чем плох текущий сборщик? Ведь все вроде были довольны?


Ну если вкратце, то не совсем. Текущий сборщик мусора, при всем своем качестве, обладает двумя проблемами — большое использование ресурсов CPU на большом числе создаваемых и быстро удаляемых объектов, и относительно низкая скорость создания объектов в heap. Обе проблемы так-же предлагалось решить с помощью улучшения escape-analysis, который бы позволял компилятору лучше доказывать тот факт, что переменная не убегает в heap. Например сейчас, если вы передаете указатель в метод интерфейса, то сам объект будет помечен как «убегающий» и помещен в heap, даже если сам метод пустой и тут же возвращается. Причина проста — текущий компилятор не может доказать, что конечная реализация интерфейса гарантирует время жизни объекта по указателю в вызывающей функции дольше, чем время жизни указателя внутри метода. Сами же улучшения в escape-analysis это сложная работа, конкретные результаты который сейчас очень сложно прикинуть.


Другая проблема текущего подхода это овер-аллокация. Сочетание текущего pacer’а и стратегии увеличения памяти (обычно x2 от текущей используемой), порой приводит к резкому росту памяти когда в случае внезапных скачков нагрузки, GC не успевает за «новыми реалиями». Это особенно чувствительно когда у вас есть лимиты — например не больше 10 GB на контейнер. Core Team Go знают об этой проблеме, и у них есть варианты ее решения. Так например в Go 1.12 планируется набор улучшений который позволит сборщику мусора быстрее находить «мусорную» память и ее переиспользовать. Но концептуально вопрос остается открыт.


Возвращаясь к Generational GC — дополнительным плюсом такого подхода является дополнительная возможность для различных оптимизаций создания объектов в heap’е. Конечно же, есть и минусы: главным из них является тот факт, что такой вид сборщика связан с неопределенностью во времени сборки мусора — тем фактом, которым мы так дорожим в мире Go.


Однако судя по комментариям в коде, эту проблему авторам Go удалось решить. Насколько хорошо? Время покажет…


Дополнительные материалы для чтения:

- https://engineering.linecorp.com/en/blog/go-gc/

- https://medium.com/@MartinCracauer/generational-garbage-collection-write-barriers-write-protection-and-userfaultfd-2-8b0e796b8f7f

Report Page