предисловие
В блоге V8 недавно была опубликована статья, описывающая новый метод «параллельной маркировки», повышающий эффективность процесса маркировки.
Concurrency Marking — это проект, который в основном заменяет старый сборщик мусора новым параллельным и параллельным сборщиком мусора, который теперь включен по умолчанию в Chrome 64 и Node.js v10. Прежде чем объяснять, давайте рассмотрим основные моменты знаний.
основная концепция
Гипотеза слабого поколения
- Большинство объектов имеют короткое время жизни
- Объекты с длительным жизненным циклом, как правило, резидентные объекты
Сборщик мусора V8 также основан на предположении, что объекты делятся на два поколения: молодое поколение и старое поколение.
Реализация разных алгоритмов для разных поколений может более эффективно выполнять сборку мусора.
Новое поколение и старое поколение
Новое поколение включает в себя новое пространство, а старое поколение включает в себя: старое пространство, пространство кода и пространство карты, пространство больших объектов.
Движок V8 в 64-разрядной среде имеет размер памяти нового поколения 32 МБ и размер памяти старого поколения 1400 МБ, в то время как объем памяти 32-разрядной версии уменьшен вдвое, 16 МБ и 700 МБ соответственно.
Для объектов нового поколения используется алгоритм Scavenge of space for time для максимально быстрого освобождения памяти. Если объект все еще силен после 2 GC, он будет переведен в старое поколение (точнее, сохранен в Old Space) во второй коллекции.
GC старого поколения принимает алгоритм Mark-Sweep и использует Mark-Sweep для решения проблемы фрагментации памяти.
Алгоритм очистки
Для объектов нового поколения для их восстановления используется алгоритм Scavenge.
Проще говоря, пространство памяти разделено на два полупространства, и одновременно используется только одно пространство. Используемый вызывается в космос, а неиспользуемый вызывается из космоса.
При размещении объектов сначала размещайте их в пространстве From, проверяйте (сначала в ширину) уцелевшие объекты в пространстве From во время сборки мусора, копируйте уцелевшие объекты в пространство To и очищайте невыжившие объекты. космические тождества меняются местами.
Алгоритм маркировки-развертки
При обработке объектов старого поколения, первое сканирование глубины, алгоритм трехцветной маркировки.
V8 реализует маркировку с использованием двух битов метки на объект и рабочего стека маркировки.
Два бита метки кодируют три цвета: белый (00), серый (10) и черный (11).
Белый означает, что объект может быть переработан, черный означает, что объект не может быть переработан, и все его ссылки были облегчены, а серый означает, что он не может быть переработан, и его эталонные объекты не были отсканированы.
Процесс сканирования:
- Начните с известных объектов, то есть корней (глобальные объекты и функции активации), и отметьте все некорневые объекты белым цветом.
- Все прямые ссылки на корневой стек объектов объектов (рабочий список маркировки)
- Вне POP, из стека объектов, помеченных как черный, удерживая свой объект прямой ссылки как серый и помещая стек
- Когда стек пуст, объекты, которые все еще белые, могут быть переработаны.
- переработка белых предметов
На этапе очистки очищаются только непомеченные объекты.
Однако после очистки память окажется в прерывистом состоянии, что приведет к бессмысленному повторному использованию последующих адресов выделения больших объектов (из-за нехватки доступной памяти).В это время для борьбы с фрагментацией памяти требуется Mark-Compact.
Алгоритм Марк-Компакт
После того, как объект помечен как мертвый, в процессе сортировки переместите живой объект на другую страницу памяти.После перемещения страницу памяти можно вернуть в операционную систему, но если на активный объект этой страницы ссылается множество объектов других страниц компактно не будет, т.к. обновление указателей других ссылок после перемещения дорого.
Полная пауза и дополнительные маркеры
Три основных алгоритма сборки мусора требуют приостановки логики приложения и возобновления логики приложения после сборки мусора, то есть «полной паузы». Длительная пауза заставит пользователей чувствовать себя застрявшими. Поэтому, чтобы уменьшить сборку мусора всей кучи, когда куча достигает определенного уровня, начинается инкрементальный сборщик мусора.V8 делит действие маркировки на множество небольших «шагов» в фазе маркировки, и логика приложения и сборка мусора чередуются до тех пор, пока фаза маркировки не будет завершена .
Тем не менее, для слишком больших куч все еще существуют длительные паузы в сборщике мусора, пытающемся не отставать от скорости выделения приложения, и приложению необходимо уведомлять сборщик мусора обо всех изменениях в графе объектов, которые имеют свою цену (гарантированная запись). пиши -барьер).
V8 использует барьер записи в стиле Дейкстры для реализации уведомлений.
Когда object.field = value в JavaScript, V8 вставляет следующий код:
// Called after `object.field = value`.
write_barrier(object, field_offset, value) {
if (color(object) == black && color(value) == white) {
set_color(value, grey);
marking_worklist.push(value);
}
}
Барьер записи может гарантировать, что черный объект не будет указывать на белый объект (строгий трехцветный инвариант), поэтому приложение не удалит активный объект по ошибке во время GC. Все белые объекты можно безопасно удалить после завершения GC.
Однако из-за потери барьера записи производительность приложения снижается, поэтому для повышения пропускной способности необходимо использовать другие рабочие потоки, чтобы рабочие потоки также могли выполнять отмеченную работу. Это параллельная маркировка и параллельная маркировка, которые будут обсуждаться ниже.
параллельная маркировка
Во время параллельной маркировки приложение приостанавливается, и основной поток и рабочий поток совместно выполняют операцию маркировки.На следующем рисунке показаны структуры данных, задействованные в параллельной маркировке. Стрелки указывают направление потока данных.
Среди них граф объектов доступен только для чтения, и его нельзя изменять, биты меток и рабочий список маркировки можно читать и записывать.
Рабочий список маркировки отвечает за определение рабочей нагрузки, выделенной другим рабочим потокам, определение баланса между производительностью и обслуживанием локальных потоков, поэтому решающее значение имеет то, как эффективно выполнить распределение работы.
Как показано на рисунке ниже, V8 использует подход на основе сегментов памяти для балансировки рабочей нагрузки каждого потока, избегая трудоемкой синхронизации потоков и выполняя как можно больше работы.
параллельная маркировка параллельная маркировка
Параллельная маркировка позволяет выполнять действия по маркировке одновременно с приложением. Это должно решить проблему гонок данных, например, JS-код меняет поля объекта, а рабочий поток помечает поля, что может привести к неправильной сборке мусора.
Таким образом, основной поток должен синхронизироваться с рабочими потоками, когда происходит конкуренция данных.Большинство действий, связанных с конкуренцией данных, можно синхронизировать с помощью облегченного атомарного доступа к памяти, но в некоторых особых сценариях требуется монопольный доступ ко всему объекту.
оптимизированные результаты
При параллельной маркировке и одновременной маркировке процесс GC становится:
- Начать сканирование с корневого объекта, заполнить объект рабочим списком маркировки
- Распределить одновременные задачи маркировки на рабочие потоки
- Рабочие потоки помогают основному потоку быстрее использовать объекты в рабочем списке маркировки.
- Основной поток время от времени помечает, выполняя аварийный рабочий список и помечающий рабочий список.
- Как только рабочие списки маркировки пусты, основной поток завершает поведение GC.
- Перед окончанием основной поток повторно сканирует корни и может найти другие белые узлы, которые будут отмечены параллельно с помощью рабочих потоков.
использованная литература:
- Картирование разума:GitHub.com/Бай Линьлин/А…