Перейти к основному содержимому

Получение сборки мусора бесплатно

· 8 мин. чтения
Ханнес Пайер и Росс МакИлрой, Idle Garbage Collectors

Производительность JavaScript продолжает оставаться одним из ключевых аспектов ценностей Chrome, особенно когда речь идет о обеспечении плавного взаимодействия. Начиная с Chrome 41, V8 использует новую технику для повышения отзывчивости веб-приложений, скрывая дорогостоящие операции управления памятью внутри небольших, иначе неиспользуемых промежутков времени простоя. В результате веб-разработчики могут ожидать более плавную прокрутку и идеальные анимации с значительно уменьшенными заиканиями из-за сборки мусора.

Многие современные языковые движки, такие как движок JavaScript V8 в Chrome, динамически управляют памятью для работающих приложений, чтобы разработчикам не нужно было беспокоиться об этом самостоятельно. Движок периодически проходит по памяти, выделенной приложению, определяет, какие данные больше не нужны, и очищает их, освобождая место. Этот процесс известен как сборка мусора.

В Chrome мы стремимся обеспечить плавный визуальный опыт с частотой 60 кадров в секунду (FPS). Хотя V8 уже пытается выполнять сборку мусора небольшими кусками, более крупные операции сборки мусора могут происходить в непредсказуемые моменты времени — иногда в середине анимации — останавливая выполнение и препятствуя достижению цели 60 FPS.

Chrome 41 включил планировщик задач для движка рендеринга Blink, который позволяет приоритизировать задачи, чувствительные к задержкам, чтобы Chrome оставался отзывчивым и быстрым. Помимо возможности приоритизировать работу, этот планировщик задач имеет централизованные сведения о загруженности системы, какие задачи нужно выполнить и насколько срочными являются эти задачи. Таким образом, он может оценить, когда Chrome, вероятно, будет простаивать, и приблизительно, как долго он ожидает оставаться в состоянии простоя.

Примером этого является ситуация, когда Chrome демонстрирует анимацию на веб-странице. Анимация будет обновлять экран с частотой 60 FPS, предоставляя Chrome примерно 16.6 мс времени для выполнения обновления. Таким образом, Chrome начнет работу над текущим кадром, как только предыдущий кадр будет отображен, выполняя задачи вводных данных, анимации и рендеринга кадров для нового кадра. Если Chrome завершит всю эту работу менее чем за 16.6 мс, то ему нечего больше делать до тех пор, пока не потребуется начать рендеринг следующего кадра. Планировщик Chrome позволяет V8 использовать этот период простоя, запланировывая специальные задачи простоя в моменты, когда Chrome иначе был бы свободен.

Рисунок 1: Рендеринг кадра с задачами простоя

Задачи простоя — это специальные задачи с низким приоритетом, которые выполняются, когда планировщик определяет, что система находится в состоянии простоя. Задачи простоя имеют срок выполнения, который является оценкой планировщика, как долго, по его мнению, система будет находиться в состоянии простоя. В примере с анимацией на Рисунке 1 это было бы время, когда должен начаться рендеринг следующего кадра. В других ситуациях (например, когда на экране нет никакой активности) это могло бы быть время, когда должна быть запланирована следующая задача с верхним пределом в 50 мс, чтобы гарантировать, что Chrome остается отзывчивым к неожиданному вводу пользователем. Срок выполнения используется задачей простоя для оценки того, сколько работы можно выполнить, не вызывая заикания или задержек в ответах на вводимые данные.

Сборка мусора, выполняемая в задачах простоя, скрыта от критических операций, чувствительных к задержкам. Это означает, что эти задачи сборки мусора выполняются «бесплатно». Чтобы понять, как V8 это делает, стоит ознакомиться с текущей стратегией сборки мусора V8.

Глубокое погружение в движок сборки мусора V8

V8 использует поколенческий сборщик мусора, в котором куча JavaScript разделена на небольшое молодое поколение для недавно выделенных объектов и большое старое поколение для долгоживущих объектов. Поскольку большинство объектов умирает молодыми, эта поколенческая стратегия позволяет сборщику мусора регулярно выполнять короткие сборки мусора в меньшем молодом поколении (известные как уборки), не отслеживая объекты в старом поколении.

Молодое поколение использует стратегию размещения полупространства, где новые объекты первоначально размещаются в активном полупространстве молодого поколения. Когда это полупространство становится заполненным, операция сборки освобождает живые объекты и перемещает их в другое полупространство. Объекты, которые были перемещены один раз, продвигаются в старшее поколение и считаются долгоживущими. После перемещения живых объектов новое полупространство становится активным, а оставшиеся мертвые объекты в старом полупространстве отбрасываются.

Продолжительность операции сборки в молодом поколении зависит от размера живых объектов в молодом поколении. Сборка происходит быстро (<1 мс), когда большинство объектов становятся недоступными в молодом поколении. Однако, если большинство объектов выживает после сборки, продолжительность операции может быть значительно длиннее.

Основная сборка всей кучи выполняется, когда размер живых объектов в старшем поколении превышает эвристически определенный предел. Старшее поколение использует маркировку и очистку с несколькими оптимизациями для улучшения задержки и сокращения потребления памяти. Задержка маркировки зависит от количества живых объектов, которые нужно маркировать, причем маркировка всей кучи может занять более 100 мс для крупных веб-приложений. Чтобы избежать паузы основного потока на такой длительный период, V8 давно имеет возможность постепенно маркировать живые объекты небольшими шагами, стремясь к тому, чтобы продолжительность каждого шага маркировки была менее 5 мс.

После маркировки свободная память снова становится доступной для приложения путем очистки всей памяти старшего поколения. Эта задача выполняется одновременно выделенными потоками очистки. В конечном счете проводится уплотнение памяти для уменьшения фрагментации памяти в старшем поколении. Эта задача может быть очень трудоемкой и выполняется только в том случае, если существует проблема фрагментации памяти.

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

  1. Сборка молодого поколения, которая обычно происходит быстро
  2. Шаги маркировки, выполняемые постепенным маркером, которые могут быть сколь угодно длинными в зависимости от размера шага
  3. Полные сборки мусора, которые могут занимать много времени
  4. Полные сборки мусора с агрессивным уплотнением памяти, которые могут занимать много времени, но устраняют фрагментированную память

Чтобы выполнять эти операции в периоды простоя, V8 отправляет задачи сборки мусора в простой к планировщику. Когда эти задачи выполняются, им предоставляется крайняя дата завершения. Обработчик времени простоя для сборки мусора в V8 оценивает, какие задачи сборки мусора должны быть выполнены для сокращения потребления памяти, соблюдая крайний срок, чтобы избежать будущих задержек в рендеринге кадров или вводе.

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

Когда размер живых объектов в старшем поколении приближается к пределу кучи, начинается постепенная маркировка. Шаги постепенной маркировки могут быть линейно масштабированы в зависимости от количества байтов, которые должны быть маркированы. На основе средней измеренной скорости маркировки обработчик времени простоя сборки мусора пытается вместить как можно больше работы по маркировке в данную задачу простоя.

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

Оценка производительности

Чтобы оценить влияние выполнения сборки мусора в период простоя, мы использовали инструмент измерения производительности Telemetry Chrome для оценки плавности прокрутки популярных веб-сайтов во время их загрузки. Мы протестировали топ-25 сайтов на рабочей станции с Linux, а также типичные мобильные сайты на смартфоне Android Nexus 6, оба из которых открывают популярные веб-страницы (включая сложные веб-приложения, такие как Gmail, Google Docs и YouTube) и прокручивают их контент в течение нескольких секунд. Chrome стремится поддерживать прокрутку на уровне 60 кадров в секунду для обеспечения плавности работы пользователя.

На рисунке 2 показан процент сборок мусора, запланированных в период простоя. Более быстрое оборудование рабочей станции приводит к большему общему времени простоя по сравнению с Nexus 6, что позволяет запланировать больший процент сборок мусора в данный период простоя (43% по сравнению с 31% на Nexus 6), что приводит к улучшению примерно на 7% в нашем метрике задержек.

Рисунок 2: Процент сборки мусора, происходящей во время простоя

Помимо улучшения плавности рендеринга страницы, эти периоды простоя также предоставляют возможность проводить более агрессивную сборку мусора, когда страница становится полностью неактивной. Недавние улучшения в Chrome 45 используют это, чтобы значительно сократить объем памяти, потребляемой вкладками на переднем плане, находящимися в режиме простоя. Рисунок 3 показывает, как использование памяти JavaScript кучи Gmail может быть сокращено примерно на 45% в состоянии простоя по сравнению с той же страницей в Chrome 43.

Рисунок 3: Использование памяти для Gmail в последней версии Chrome 45 (слева) vs. Chrome 43

Эти улучшения демонстрируют, что можно скрыть паузы сборки мусора, будучи более умными в выборе момента для выполнения дорогостоящих операций сборки мусора. Веб-разработчикам больше не нужно бояться пауз сборки мусора, даже при создании невероятно плавной анимации с частотой 60 FPS. Оставайтесь с нами, чтобы узнать о новых улучшениях по мере расширения возможностей планирования сборки мусора.