Узнайте, как измерить использование памяти вашей веб-страницы в процессе работы, чтобы обнаружить регрессии.
Браузеры автоматически управляют памятью веб-страниц. Всякий раз, когда веб-страница создает объект, браузер выделяет часть памяти «под капотом» для хранения объекта. Поскольку память является конечным ресурсом, браузер выполняет сборку мусора, чтобы определить, когда объект больше не нужен, и освободить базовый участок памяти.
Однако обнаружение не идеально, и было доказано , что идеальное обнаружение — невыполнимая задача. Поэтому браузеры приближают понятие «необходим объект» к понятию «объект достижим». Если веб-страница не может достичь объекта через его переменные и поля других достижимых объектов, то браузер может безопасно вернуть объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в следующем примере.
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
предоставляет дополнительную информацию об используемой памяти. Каждая запись описывает некоторую часть памяти и приписывает ее набору окон, фреймов и рабочих процессов, идентифицированных по URL. Поле types
перечисляет типы памяти, специфичные для реализации, связанные с памятью.
Важно обрабатывать все списки обобщенно и не задавать жесткие предположения, основанные на конкретном браузере. Например, некоторые браузеры могут возвращать пустую breakdown
или пустую attribution
. Другие браузеры могут возвращать несколько записей в attribution
указывая на то, что они не смогли различить, какая из этих записей владеет памятью.
Обратная связь
Группа сообщества Web Performance и команда Chrome будут рады услышать ваши мысли и опыт использования performance.measureUserAgentSpecificMemory()
.
Расскажите нам о дизайне API
Есть ли что-то в API, что не работает так, как ожидалось? Или отсутствуют свойства, которые вам нужны для реализации вашей идеи? Отправьте запрос спецификации в репозиторий performance.measureUserAgentSpecificMemory() GitHub или добавьте свои мысли в существующую проблему.
Сообщить о проблеме с реализацией
Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на new.crbug.com . Обязательно включите как можно больше подробностей, предоставьте простые инструкции по воспроизведению ошибки и установите Components на Blink>PerformanceAPIs
.
Показать поддержку
Планируете ли вы использовать performance.measureUserAgentSpecificMemory()
? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты функций и показывает другим поставщикам браузеров, насколько важно их поддерживать. Отправьте твит @ChromiumDev и расскажите нам, где и как вы его используете.
Полезные ссылки
- Объяснитель
- Демо | Источник демо
- Ошибка отслеживания
- Запись ChromeStatus.com
- Изменения по сравнению с Origin Trial API
- Завершенный судебный процесс по происхождению
Благодарности
Большое спасибо Доменику Дениколе, Йоаву Вайсу, Матиасу Байненсу за обзоры дизайна API, а также Доминику Инфюру, Ханнесу Пайеру, Кентаро Харе, Михаэлю Липпаутцу за обзоры кода в Chrome. Я также благодарю Пера Паркера, Филиппа Вайса, Ольгу Беломестных, Мэтью Болохана и Нила Маккея за предоставление ценных отзывов пользователей, которые значительно улучшили API.
Изображение главного героя от Харрисона Бродбента на Unsplash