🕐 Как в Google Chrome измерить использование памяти веб-страницей
Держим руку на пульсе производительного веба. В этой статье учимся измерять утечки памяти страницы Google Chrome с помощью нового интерфейса performance.measureMemory().
Когда веб-страница создаёт объект, браузер выделяет память для его хранения. Но объем памяти ограничен, и время от времени браузер выполняет сборку мусора. Однако если веб-страница не может достичь объекта через переменные и поля других доступных объектов, она может попытаться восстановить объект. Эти манипуляции приводят к утечкам памяти:
Массив b
больше
не нужен, но браузер его не восстанавливает, так как массив всё ещё доступен через
object.b
в коллбэке. То есть в большем массиве и происходит утечка
памяти.
К сожалению, утечки памяти широко
распространены в веб-разработке: здесь забыли отменить регистрацию event
listener
, там случайно начали использовать объекты из iframe
, а тут накапливаем объекты в
массивах... Утечки приводят к тому, что использование памяти страницей растёт, и она работает медленно.
Первый шаг в решении проблемы – её измерение. Новый API
performance.measureMemory()
позволяет разработчикам измерять использование
памяти страницей в процессе её работы и обнаружить утечки, которые могли проскользнуть во время тестирования.
Чем performance.measureMemory() отличается от performance.memory()?
Основное отличие старого и нового API заключается в том, что старый API возвращает размер кучи JavaScript, в то время как новый оценивает использование памяти веб-страницей целиком. Это различие становится особенно важным, когда в браузере одну и ту же кучу используют несколько веб-страниц или несколько экземпляров одной и той же страницы.
Еще одно отличие заключается в том, что новый API выполняет измерение памяти во время сборки мусора. Это уменьшает погрешность в результатах, но измерение может быть более продолжительным. Обратите внимание, что другие браузеры могут решить реализовать новый API, не учитывая сборку мусора.
Предлагаемые варианты использования
Использование памяти
зависит от времени событий, действий пользователя и сборок мусора. Поэтому API measureMemory()
предназначен для агрегирования данных об использовании
памяти непосредственно в продакшене. Примеры использования:
- обнаружение регрессии во время развертывания новой версии веб-страницы и фиксирования новых утечек памяти;
- прогон A/B-тестирования новой функции для оценки ее влияния на память и обнаружение утечек;
- корреляция использования памяти с продолжительностью сеанса;
- корреляция использования памяти с пользовательскими метриками для понимания общего влияния на память.
Совместимость браузеров
На момент написания материала API поддерживался только в Chrome 83 в качестве пробной версии. Результат работы API сильно зависит от реализации, поскольку браузеры имеют разные способы представления объектов в памяти и разные способы оценки ее использования. Браузеры могут исключить некоторые области памяти из учета, если надлежащий учет слишком затратен или неосуществим. Таким образом, результаты в разных браузерах сравнить не получится.
Использование performance.measureMemory()
Пробная версия позволит вам попробовать новые функции и дать обратную связь об их удобстве, практичности и эффективности сообществу веб-стандартов. Дополнительные сведения можно найти в гайде для веб-разработчиков. Чтобы использовать эту или другую пробную версию инструмента, посетите страницу регистрации.
Запросите токен и добавьте его на свои страницы. Есть два способа:
- Добавьте тег
origin-trial <meta>
, в head каждой страницы. Это может выглядеть примерно так:<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
- Если вы можете попасть на свой сервер, добавьте токен и результирующий заголовок ответа будет выглядеть примерно так:
Origin-Trial: TOKEN_GOES_HERE
Для того чтобы экспериментировать с
performance.measureMemory()
без «танцев» с пробной версией, активируйте флаг
#experimental-web-platform-features
в chrome://flags.
Некоторые особенности
Функция performance.measureMemory()
может завершиться ошибкой SecurityError,
если среда выполнения не удовлетворяет требованиям безопасности для
предотвращения cross-origin утечки информации. Чтобы этого избежать, инструмент требует наличие включенной изоляции сайта.
Веб-страница может использовать cross-origin изоляцию, установив заголовки COOP+COEP:
Локальное тестирование
Chrome выполняет
измерение памяти во время сборки мусора. Это означает, что API не выдает результаты
незамедлительно – вместо этого он ожидает следующей сборки мусора. API провоцирует
сборку мусора после некоторого таймаута (сейчас это 20 секунд). Запустите
Chrome с флагом --enable-blink-features='ForceEagerMeasureMemory'
. Это уменьшит
тайм-аут до нуля и будет полезно для локальной отладки и тестирования.
Пример
Рекомендуемое использование API заключается в определении глобального монитора памяти, который проверяет использование памяти всей веб-страницы и отправляет результаты на сервер для агрегирования и анализа.
Самый простой способ – периодически делать выборку, например, каждые n минут. Однако это привёдет к смещению данных, поскольку скачки памяти могут возникать между выборками. В следующем примере показано, как выполнять измерения с помощью процесса Пуассона, который гарантирует, что выборки с одинаковой вероятностью будут происходить в любой момент времени (демо, источник).
Определите функцию,
которая планирует следующее измерение с помощью setTimeout()
с рандомным
интервалом. Функция должна быть вызвана после загрузки страницы.
Функция
measurementInterval()
вычисляет рандомный интервал в миллисекундах таким
образом, что в среднем происходит одно измерение каждые пять минут. Изучите экспоненциальное распределение, если вас интересует матчасть, лежащая в основе
функции.
Наконец, асинхронная
функция performMeasurement()
вызывает API, записывает результат и планирует
следующее измерение.
Результат должен выглядеть примерно так:
Общая оценка использования памяти возвращается в байтах. Значение байтов определяется с помощью синтаксиса числового разделителя. Это значение зависит от реализации и не может быть сопоставлено между браузерами. Во время использования нашей origin trial версии значение несет в себе использование памяти главного окна и всех iframes того же сайта, плюс связанных с ними окон.
Список breakdown
содержит дополнительную информацию об используемой памяти. Каждая запись
описывает некоторую ее часть и приписывает ее набору окон, ifram-ов и воркеров,
идентифицируемых URL-адресами. В поле userAgentSpecificTypes
перечислены
конкретные для реализации типы, связанные с памятью.
Важно рассматривать все
списки в общем виде, а не основываться на конкретном браузере. Например, некоторые
браузеры могут возвращать пустой breakdown
или пустые атрибуты. Другие браузеры
могут возвращать несколько URL-адресов в атрибуте, указывая, что они не могут
различить, какой из этих URL-адресов владеет памятью.
Обратная связь
- Web Performance Community Group и команда Chrome хотели бы услышать ваши мысли на счет опыта работы с
performance.measureMemory()
. - Если в API есть недочеты или существуют недостающие свойства, которые вам необходимы – подайте запрос спецификации на performance.measureMemory или добавьте свои мысли к существующей проблеме.
- Вы нашли ошибку в реализации Chrome? Реализация отличается от спецификации? Подайте сообщение об ошибке по адресу new.crbug.com и обязательно включите как можно больше деталей, предоставьте инструкции по воспроизведению ошибки и установите компоненты в положение
Blink > PerformanceAPIs
. Glitch отлично подходит для этих целей. - Планируете ли вы использовать
performance.measureMemory()
? Ваша публичная поддержка поможет команде Chrome определять приоритеты и покажет другим разработчикам важность происходящего. Отправьте твит на @ChromiumDev и сообщите, где и как вы его используете.