Javascript статической памяти с пулами объектов

Введение

Итак, вы получаете электронное письмо о том, что ваша веб-игра/веб-приложение через определенное время работает плохо, вы копаетесь в своем коде и не видите ничего выдающегося, пока не откроете инструменты Chrome для повышения производительности памяти и видеть это:

Снимок из вашей временной шкалы памяти

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

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

Что означают пилообразные

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

Сбор мусора и затраты на производительность

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

Сбор мусора часто изображается как противоположность ручному управлению памятью, которое требует от программиста указать, какие объекты освободить и вернуть в систему памяти.

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

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

Уменьшите отток памяти, уменьшите налоги на сбор мусора

Как уже отмечалось, импульс GC произойдет, как только набор эвристик определит, что имеется достаточно неактивных объектов, и импульс будет полезен. Таким образом, ключ к сокращению времени, затрачиваемого сборщиком мусора на ваше приложение, заключается в устранении как можно большего количества случаев чрезмерного создания и освобождения объектов. Этот процесс создания/освобождения объекта часто называют «обменом памяти». Если вы можете уменьшить отток памяти в течение срока службы вашего приложения, вы также уменьшите время, затрачиваемое сборщиком мусора на ваше выполнение. Это означает, что вам нужно удалить/уменьшить количество создаваемых и уничтоженных объектов, по сути, вам нужно прекратить выделение памяти.

Этот процесс переместит ваш график памяти отсюда:

Снимок из вашей временной шкалы памяти

к этому:

Статическая память Javascript

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

Переход к статической памяти JavaScript

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

  1. Инструментируйте свое приложение, чтобы определить максимальное количество необходимых объектов оперативной памяти (для каждого типа) для различных сценариев использования.
  2. Повторно реализуйте свой код, чтобы предварительно выделить эту максимальную сумму, а затем вручную получить/освободить ее, а не отправлять в основную память.

На самом деле, выполнение пункта 1 требует от нас немного выполнения пункта 2, так что давайте начнем с него.

Пул объектов

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

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

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

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

Предварительное выделение объектов

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

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

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

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

Далеко не серебряная пуля

Существует целая классификация приложений, в которых статические шаблоны увеличения памяти могут быть выигрышными. Однако, как отмечает коллега по Chrome DevRel Ренато Мангини , у этого решения есть несколько недостатков.

Заключение

Одна из причин, по которой JavaScript идеально подходит для Интернета, заключается в том, что это быстрый, интересный и простой язык для начала работы. В основном это связано с низким барьером для синтаксических ограничений и возможностью решения проблем с памятью от вашего имени. Вы можете написать код и позволить ему позаботиться о грязной работе. Однако для высокопроизводительных веб-приложений, таких как игры HTML5 , сборщик мусора часто может съедать критически необходимую частоту кадров , снижая удобство для конечного пользователя. Благодаря тщательному инструментированию и использованию пулов объектов вы можете снизить нагрузку на частоту кадров и освободить это время для более интересных вещей.

Исходный код

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

Рекомендации