Pull to refresh

Сборщик мусора в Go: решение проблемы отзывчивости в Go 1.5

High performance *Programming *System Programming *Go *
Translation
Original author: Sourcegraph
Данный материал представляет собой перевод блог поста, который в реальном времени ведут ребята из Sourcegraph с конференции GopherCon 2015, которая проходит в эти дни в Денвере, Колорадо. Полное видео и слайды доклада будут добавлены к посту, как только будут доступны.

Ричард Л. Хадсон (Рик) знаменит по своим работам в управлении памятью, включая изобретение алгоритмов Train, Sapphire и Mississippi Delta, а так же GC stack maps, которые позволили реализовать сборку мусора в статически-типизированных языках вроде Java, C# и Go. Под его авторством были опубликованы документы о рантаймах языков, управлении памятью, многопоточности, синхронизации, моделей памяти и транзакционной памяти. Сейчас Рик является одним из членов команды Go в Google и работает над проблемами сборщика мусора и рантайма.




В экономике существует понятие “замкнутого цикла”, когда один процесс усиливает другой, что приводит к усилению первого. Исторически, в мире компьютеров такой “замкнутый цикл” был между развитием железа и софта. Процессоры становились быстрее, что способствовало написанию более мощного софта, что, в свою очередь, стимулировало дальнейшее повышение скорости процессоров и мощностей компьютера в целом. Этот цикл работал примерно до 2004-го, когда Закон Мура перестал работать.



В наши дни, двухкратное увеличение количества транзисторов не означает двухкратное ускорение программ. Больше транзисторов == больше ядер процессора, но программы не эволюционировали настолько, чтобы использовать больше ядер. И именно из-за того, что современные программы не используют на полную катушку многоядерность, ребята, разрабатывающие процессоры, просто не добавляют больше ядер. Цикл заглох.

Долгосрочная миссия Go заключается в том, чтобы перезагрузить этот цикл, дав миру больше параллельных, конкурентных программ. Более же краткосрочная миссия — увеличить принятие Go мировым сообществом разработчиков. И одна из наибольших претензий к Go в данный момент — это слишком длинные паузы сборщика мусора.

Когда их команда изначально взялась за проблему, он, шутя, рассказывает, что их начальная реакция, как инженеров, была не решать саму проблему, а найти обходной путь, что-то вроде такого:

  • добавить на компьютеры отслеживание глаз, и запускать сборщик мусора, когда никто не смотрит
  • показывать иконку ожидания сети во время фазы сборки мусора, и винить в паузах не сборщик мусора, а задержки сети или что-нибудь ещё




Но Расс Кокс, почему-то, эти идеи завалил на корню, и они решили закатать рукава и на самом деле улучшить сборщик мусора в Go. Они пришли к алгоритму, который уменьшает паузы GC, но делает программу чуточку медленнее. Программы на Go станут чуть медленнее в обмен на гарантированно низкие паузы фаз сборки мусора.

Какие задержки мы можем различать?

  • Наносекунда: Грейс Хоппер сравнила время с дистанцией. Наносекунда — это 11.8 дюймов.
  • Микросекунда: 5.4 микросекунд это время, за которое свет проходит 1 милю в вакууме.
  • Миллисекунды:
    • 1: Последовательное чтение 1МБ с SSD.
    • 20: Чтение 1МБ с вращающегося диска.
    • 50: Порог восприятия (порог реакции)
    • 50+: Различные задержки сети
    • 300: Моргание глаза

Так сколько работы по сборке мусора мы можем успеть в одну миллисекунду?

Java GC против Go GC




Go:
  • тысячи горутин
  • синхронизация через каналы
  • рантайм написан на Go, используя его особенности, как и обычный код
  • контроль пространственной локальности (можно встраивать структуры, внутренние указатели(&foo.field))


Java:
  • десятки потоков Java
  • синхронизация через объекты/локи
  • рантайм написан на C
  • объекты связаны через указатели


Наибольшим различием тут является вопрос пространственной локальности. В Java всё является указателями, в Go же вы можете встраивать структуры одна в другую. Когда приходится заходить по ссылкам на много уровней вглубь, это может создавать массу проблем сборщику мусора.

Основы сборщика мусора


Вот краткий пример работы сборщика мусора. Обычно, она состоит из двух фаз:



  1. Фаза сканирования: определить, какие вещи в куче достижимы. Сюда входят указатели в стеках, регистры, глобальные переменные, и далее, указатели в куче.
  2. Фаза маркировки: проход по графу указателей. Пометить объекты, как достижимые по ходу исполнения программы. С точки зрения сборщика мусора, проще всего “остановить мир”, чтобы указатели не изменялись во время этой фазы вообще. По-настоящему параллельный сборщик мусора сделать очень сложно, потому что указатели постоянно изменяются. Программа использует так называемые “барьеры на запись” (write barriers), чтобы сообщать сборщику мусора, что этот объект не нужно захватывать. На практике, правда, барьеры на запись могут давать результат даже хуже, чем “остановка мира”.


Сборщик мусора в Go


Новый алгоритм сборщика мусора Go использует комбинацию барьеров на запись и коротких пауз для “остановки мира”. Вот его фазы:



Вот как выглядела работа сборщика мусора в Go 1.4:



А вот как она выглядит в Go 1.5:



Заметьте, что паузы для “остановки мира” намного короче. Во время работы параллельного сборщика мусора, он использует 25% процессора.

Вот результаты тестов:



В предыдущих версиях Go, паузы сборщика мусора были в общем случае намного длиннее и возрастали по мере роста размера кучи. В Go 1.5 паузы сборщика мусора стали более, чем на порядок меньше.

Если приблизить цифры, то видно, что всё ещё есть слабая позитивная корреляция между размером кучи и длительностью пауз GC. Но они знают, в чём причина и она будет устранена в Go 1.6.



Есть небольшой штраф в производительности с новым алгоритмом, но этот штраф уменьшается по мере того, как растёт объем кучи.



Глядя в будущее


Сообщите людям, что паузы сборщика мусора больше не являются проблемой в Go. Глядя в будущее, они планируют затюнить сборщик мусора для достижения ещё более коротких пауз, большей производительности и большей предсказуемости. Они хотят найти оптимальное соотношение в этом компромисе. Разработка Go 1.6 будет зависеть от обратной связи, поэтому они очень ждут отзывов сообщества.



Новый сборщик мусора сделает Go ещё более подходящей заменой для языков с ручным управлением памятью.

Ссылки


Слайды: talks.golang.org/2015/go-gc.pdf

Дополнительно о сборщике мусора в Go


Go 1.5 concurrent garbage collector pacing
Go 1.4+ Garbage Collection (GC) Plan and Roadmap
Tags:
Hubs:
Total votes 30: ↑26 and ↓4 +22
Views 20K
Comments Comments 81