Узнайте, как измерить использование памяти вашей веб-страницы в рабочей среде, чтобы обнаружить регрессии.
Браузеры автоматически управляют памятью веб-страниц. Всякий раз, когда веб-страница создает объект, браузер выделяет «под капотом» часть памяти для хранения объекта. Поскольку память является ограниченным ресурсом, браузер выполняет сборку мусора, чтобы определить, когда объект больше не нужен, и освободить соответствующий фрагмент памяти.
Однако обнаружение не является идеальным, и было доказано , что идеальное обнаружение — невыполнимая задача. Поэтому браузеры приближают понятие «объект необходим» к понятию «объект достижим». Если веб-страница не может получить доступ к объекту через его переменные и поля других доступных объектов, браузер может безопасно вернуть объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в следующем примере.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
Здесь больший массив b
больше не нужен, но браузер не забирает его, поскольку он по-прежнему доступен через object.b
в обратном вызове. Таким образом происходит утечка памяти большего массива.
Утечки памяти широко распространены в Интернете . Его легко ввести, забыв отменить регистрацию прослушивателя событий, случайно захватить объекты из iframe, не закрыть рабочий процесс, накопить объекты в массивах и т. д. Если на веб-странице есть утечки памяти, то ее использование памяти со временем увеличивается, и веб-страница кажется пользователям медленной и раздутой.
Первым шагом в решении этой проблемы является ее измерение. Новый API performance.measureUserAgentSpecificMemory()
позволяет разработчикам измерять использование памяти их веб-страницами в рабочей среде и таким образом обнаруживать утечки памяти, которые не проходят локальное тестирование.
Чем performance.measureUserAgentSpecificMemory()
отличается от устаревшего API performance.memory
?
Если вы знакомы с существующим нестандартным API performance.memory
, вам может быть интересно, чем отличается от него новый API. Основное отличие состоит в том, что старый API возвращает размер кучи JavaScript, тогда как новый API оценивает объем памяти, используемый веб-страницей. Это различие становится важным, когда Chrome использует одну и ту же кучу с несколькими веб-страницами (или несколькими экземплярами одной и той же веб-страницы). В таких случаях результат старого API может быть произвольно отключен. Поскольку старый API определен в терминах, специфичных для реализации, таких как «куча», его стандартизация безнадежна.
Еще одно отличие состоит в том, что новый API выполняет измерение памяти во время сборки мусора. Это уменьшает шум в результатах, но получение результатов может занять некоторое время. Обратите внимание, что другие браузеры могут решить реализовать новый API, не полагаясь на сбор мусора.
Рекомендуемые варианты использования
Использование памяти веб-страницы зависит от времени событий, действий пользователя и сборки мусора. Вот почему API измерения памяти предназначен для агрегирования данных об использовании памяти в производстве. Результаты отдельных вызовов менее полезны. Примеры использования:
- Обнаружение регрессии во время развертывания новой версии веб-страницы для обнаружения новых утечек памяти.
- A/B-тестирование новой функции для оценки ее влияния на память и обнаружения утечек памяти.
- Сопоставление использования памяти с продолжительностью сеанса для проверки наличия или отсутствия утечек памяти.
- Сопоставление использования памяти с пользовательскими метриками, чтобы понять общее влияние использования памяти.
Совместимость с браузером
В настоящее время API поддерживается только в браузерах на базе Chromium, начиная с Chrome 89. Результат работы API сильно зависит от реализации, поскольку браузеры имеют разные способы представления объектов в памяти и разные способы оценки использования памяти. Браузеры могут исключить некоторые области памяти из учета, если правильный учет слишком дорог или невозможен. Таким образом, результаты нельзя сравнивать в разных браузерах. Имеет смысл сравнивать результаты только для одного и того же браузера.
Использование performance.measureUserAgentSpecificMemory()
Обнаружение функций
Функция performance.measureUserAgentSpecificMemory
будет недоступна или может завершиться сбоем с ошибкой SecurityError , если среда выполнения не соответствует требованиям безопасности для предотвращения утечек информации из разных источников. Он опирается на изоляцию между источниками , которую веб-страница может активировать, установив заголовки COOP+COEP .
Поддержка может быть обнаружена во время выполнения:
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
} else {
throw error;
}
}
console.log(result);
}
Локальное тестирование
Chrome выполняет измерение памяти во время сборки мусора. Это означает, что API не выполняет обещание результата немедленно, а вместо этого ждет следующей сборки мусора.
Вызов API приводит к принудительной сборке мусора по истечении некоторого времени ожидания, которое в настоящее время установлено на 20 секунд, но может произойти и раньше. Запуск Chrome с флагом командной строки --enable-blink-features='ForceEagerMeasureMemory'
сокращает время ожидания до нуля и полезно для локальной отладки и тестирования.
Пример
Рекомендуемое использование API — определить глобальный монитор памяти, который измеряет использование памяти всей веб-страницы и отправляет результаты на сервер для агрегирования и анализа. Самый простой способ — периодически производить выборку, например, каждые M
минут. Однако это приводит к смещению данных, поскольку между выборками могут возникать пики памяти.
В следующем примере показано, как выполнить несмещенные измерения памяти с помощью процесса Пуассона , который гарантирует, что выборки с равной вероятностью возникнут в любой момент времени ( демо , исходный код ).
Сначала определите функцию, которая планирует следующее измерение памяти с помощью setTimeout()
со случайным интервалом.
function scheduleMeasurement() {
// Check measurement API is available.
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
console.log('See https://web.dev/coop-coep/ to learn more')
return;
}
if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
return;
}
const interval = measurementInterval();
console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
setTimeout(performMeasurement, interval);
}
Функция measurementInterval()
вычисляет случайный интервал в миллисекундах, так что в среднем каждые пять минут выполняется одно измерение. См. Экспоненциальное распределение , если вас интересует математика этой функции.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Наконец, асинхронная функция performMeasurement()
вызывает API, записывает результат и планирует следующее измерение.
async function performMeasurement() {
// 1. Invoke performance.measureUserAgentSpecificMemory().
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
return;
}
// Rethrow other errors.
throw error;
}
// 2. Record the result.
console.log('Memory usage:', result);
// 3. Schedule the next measurement.
scheduleMeasurement();
}
Наконец, приступайте к измерениям.
// Start measurements.
scheduleMeasurement();
Результат может выглядеть следующим образом:
// Console output:
{
bytes: 60_100_000,
breakdown: [
{
bytes: 40_000_000,
attribution: [{
url: 'https://example.com/',
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 20_000_000,
attribution: [{
url: 'https://example.com/iframe',
container: {
id: 'iframe-id-attribute',
src: '/iframe',
},
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 100_000,
attribution: [],
types: ['DOM']
},
],
}
Общая оценка использования памяти возвращается в поле bytes
. Это значение сильно зависит от реализации и не может сравниваться между браузерами. Он может даже меняться в разных версиях одного и того же браузера. Значение включает память JavaScript и DOM для всех iframe, связанных окон и веб-воркеров в текущем процессе.
Список breakdown
предоставляет дополнительную информацию об используемой памяти. Каждая запись описывает некоторую часть памяти и приписывает ее набору окон, iframe и воркеров, идентифицированных по URL-адресу. В поле types
перечислены типы памяти, связанные с конкретной реализацией.
Важно относиться ко всем спискам общим образом и не жестко запрограммировать предположения, основанные на конкретном браузере. Например, некоторые браузеры могут возвращать пустую breakdown
или пустую attribution
. Другие браузеры могут возвращать несколько записей attribution
, указывая, что они не могут определить, какой из этих записей принадлежит память.
Обратная связь
Группа сообщества Web Performance и команда Chrome хотели бы услышать ваши мысли и опыт использования performance.measureUserAgentSpecificMemory()
.
Расскажите нам о дизайне API
Что-то в API работает не так, как ожидалось? Или вам не хватает свойств, необходимых для реализации вашей идеи? Сообщите о проблеме спецификации в репозитории Performance.measureUserAgentSpecificMemory() на GitHub или добавьте свои мысли к существующей проблеме.
Сообщить о проблеме с реализацией
Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на сайте new.crbug.com . Обязательно укажите как можно больше подробностей, предоставьте простые инструкции по воспроизведению ошибки и установите для параметра «Компоненты» значение Blink>PerformanceAPIs
. Glitch отлично подходит для быстрого и простого обмена репродукциями.
Показать поддержку
Планируете ли вы использовать performance.measureUserAgentSpecificMemory()
? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в функциях и показывает другим поставщикам браузеров, насколько важно их поддерживать. Отправьте твит @ChromiumDev и сообщите нам, где и как вы его используете.
Полезные ссылки
- Объяснитель
- Демо | Демо-исходник
- Ошибка отслеживания
- Запись ChromeStatus.com
- Изменения после пробной версии Origin API
- Завершенное испытание происхождения
Благодарности
Большое спасибо Доменику Дениколе, Йоаву Вайсу, Матиасу Биненсу за обзоры дизайна API, а также Доминику Инфюру, Ханнесу Пайеру, Кентаро Хара, Михаэлю Липпаутцу за обзоры кода в Chrome. Я также благодарю Пера Паркера, Филиппа Вайса, Ольгу Беломестных, Мэтью Болохана и Нила Маккея за ценные отзывы пользователей, которые значительно улучшили API.