У меня есть довольно сложное приложение Javascript, у которого есть основной цикл, который вызывается 60 раз в секунду. Похоже, что происходит большая сборка мусора (на основе «пилообразного» вывода из временной шкалы памяти в инструментах разработчика Chrome) - и это часто влияет на производительность приложения.
Итак, я пытаюсь исследовать передовые методы сокращения объема работы, которую должен выполнять сборщик мусора. (Большая часть информации, которую я смог найти в Интернете, касается предотвращения утечек памяти, это немного другой вопрос - моя память освобождается, просто происходит слишком много сборок мусора.) Я предполагаю что это в основном сводится к максимально возможному повторному использованию объектов, но, конечно, дьявол кроется в деталях.
Приложение структурировано по «классам» в соответствии с «Простым наследованием JavaScript» Джона Ресига .
Я думаю, одна проблема заключается в том, что некоторые функции могут вызываться тысячи раз в секунду (поскольку они используются сотни раз на каждой итерации основного цикла) и, возможно, локальные рабочие переменные в этих функциях (строки, массивы и т. Д.) может быть проблема.
Мне известно об объединении объектов для более крупных / тяжелых объектов (и мы в определенной степени это используем), но я ищу методы, которые можно применять повсеместно, особенно в отношении функций, которые вызываются очень много раз в жестких циклах. .
Какие методы я могу использовать, чтобы уменьшить объем работы, которую должен выполнять сборщик мусора?
И, возможно, также - какие методы можно использовать, чтобы определить, какие объекты собираются мусором больше всего? (Это очень большая кодовая база, поэтому сравнение снимков кучи не очень плодотворно)
источник
Ответы:
Многие вещи, которые вам нужно сделать, чтобы свести к минимуму отток сборщика мусора, противоречат тому, что в большинстве других сценариев считается идиоматическим JS, поэтому при оценке рекомендаций, которые я даю, учитывайте контекст.
Распределение происходит в современных интерпретаторах в нескольких местах:
new
помощью буквального синтаксиса или с его помощью[...]
, или{}
.(function (...) { ... })
.Object(myNumber)
илиNumber.prototype.toString.call(42)
Array.prototype.slice
.arguments
чтобы отразить список параметров.Избегайте этого, объединяйте и повторно используйте объекты, где это возможно.
В частности, ищите возможности:
split
или совпадений регулярных выражений, поскольку каждое требует выделения нескольких объектов. Это часто происходит с ключами в таблицах поиска и динамическими идентификаторами узлов DOM. Например,lookupTable['foo-' + x]
и то , иdocument.getElementById('foo-' + x)
другое связано с распределением, поскольку существует конкатенация строк. Часто вы можете прикреплять ключи к долгоживущим объектам вместо повторной конкатенации. В зависимости от поддерживаемых браузеров вы можетеMap
использовать объекты как ключи напрямую.try { op(x) } catch (e) { ... }
, чтобы делатьif (!opCouldFailOn(x)) { op(x); } else { ... }
.JSON.stringify
которая использует внутренний собственный буфер для накопления содержимого вместо выделения нескольких объектов.arguments
функций Since, которые используют их, при вызове должны создавать объект, подобный массиву.Я предложил использовать
JSON.stringify
для создания исходящих сетевых сообщений. Разбор входных сообщений с использованием,JSON.parse
очевидно, включает в себя выделение, и его много для больших сообщений. Если вы можете представить свои входящие сообщения как массивы примитивов, вы можете сэкономить много распределений. Единственная другая встроенная функция, вокруг которой вы можете построить парсер, который не выделяет память, - этоString.prototype.charCodeAt
. Парсер для сложного формата, который использует только это, будет адски читать.источник
JSON.parse
d-объекты занимают меньше (или равно) места, чем строка сообщения?В инструментах разработчика Chrome есть очень хорошая функция для отслеживания распределения памяти. Это называется шкалой памяти. В этой статье описаны некоторые детали. Полагаю, это то, о чем вы говорите, о "пилообразной"? Это нормальное поведение для большинства сред выполнения с GC. Распределение продолжается до тех пор, пока не будет достигнут порог использования, запускающий сбор. Обычно на разных порогах присутствуют разные виды коллекций.
Сборки мусора включаются в список событий, связанных с трассировкой, вместе с их продолжительностью. На моем довольно старом ноутбуке эфемерные коллекции занимают около 4 МБ и занимают 30 мс. Это 2 из ваших итераций цикла 60 Гц. Если это анимация, вероятно, заикание вызывают коллекции 30 мс. Вы должны начать здесь, чтобы увидеть, что происходит в вашей среде: где находится порог сбора и сколько времени занимает ваша коллекция. Это дает вам ориентир для оценки оптимизаций. Но вы, вероятно, не добьетесь большего успеха, чем уменьшите частоту заикания, снизив скорость выделения и увеличив интервал между сборками.
Следующим шагом будет использование Профилей | Функция выделения кучи записей для создания каталога распределений по типу записи. Это быстро покажет, какие типы объектов потребляют больше всего памяти в течение периода трассировки, что эквивалентно скорости выделения. Сосредоточьтесь на них в порядке убывания.
Методы - это не ракетостроение. Избегайте упакованных объектов, когда вы можете обойтись без упаковки. Используйте глобальные переменные для хранения и повторного использования отдельных упакованных объектов, а не для выделения новых на каждой итерации. Объединяйте общие типы объектов в свободные списки, а не откажитесь от них. Кэшируйте результаты конкатенации строк, которые, вероятно, можно будет повторно использовать в будущих итерациях. Избегайте выделения только для возврата результатов функции, вместо этого задавая переменные во включающей области. Вам нужно будет рассмотреть каждый тип объекта в отдельном контексте, чтобы найти лучшую стратегию. Если вам нужна помощь по конкретным вопросам, опубликуйте правку, описывающую детали задачи, которую вы рассматриваете.
Я не рекомендую искажать ваш обычный стиль кодирования во всем приложении, пытаясь произвести меньше мусора. Это по той же причине, по которой вы не должны преждевременно оптимизировать скорость. Большая часть ваших усилий плюс большая часть дополнительной сложности и неясности кода будут бессмысленными.
источник
request animation frame
,animation frame fired
, иcomposite layers
. Понятия не имею, почему я не вижу,GC Event
как вы (это последняя версия Chrome, а также канарейка).@342342
иcode relocation info
.Как правило, вам нужно кэшировать как можно больше и делать как можно меньше создания и уничтожения при каждом запуске вашего цикла.
Первое, что приходит мне в голову, - это уменьшить использование анонимных функций (если они у вас есть) внутри вашего основного цикла. Также было бы легко попасть в ловушку создания и уничтожения объектов, которые передаются другим функциям. Я ни в коем случае не эксперт по javascript, но могу предположить, что это:
будет работать намного быстрее, чем это:
Бывает ли время простоя вашей программы? Может быть, вам нужно, чтобы он работал плавно в течение секунды или двух (например, для анимации), а затем у него было больше времени для обработки? Если это так, я мог бы видеть, как берут объекты, которые обычно собираются мусором на протяжении всей анимации, и сохраняют ссылку на них в каком-то глобальном объекте. Затем, когда анимация закончится, вы можете очистить все ссылки и позволить сборщику мусора сделать свою работу.
Извините, если все это немного тривиально по сравнению с тем, что вы уже пробовали и о чем думали.
источник
Я бы сделал один или несколько объектов в
global scope
(где я уверен, что сборщик мусора не может прикасаться к ним), затем я бы попытался реорганизовать свое решение, чтобы использовать эти объекты для выполнения работы вместо использования локальных переменных .Конечно, это невозможно сделать везде в коде, но в целом это мой способ избежать сборщика мусора.
PS Это может сделать эту конкретную часть кода немного менее удобной в обслуживании.
источник