Отслеживайте общее использование памяти вашей веб-страницы с помощью метода MeasureUserAgentSpecificMemory().

Узнайте, как измерять использование памяти вашей веб-страницы в рабочей среде для выявления регрессий.

Брендан Кенни
Brendan Kenny
Улан Дегенбаев
Ulan Degenbaev

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

Однако обнаружение не идеально, и проблема остановки Алана Тьюринга доказала, что идеальное обнаружение — невыполнимая задача. Поэтому браузеры аппроксимируют понятие «объект необходим» понятием «объект достижим». Если веб-страница не может получить доступ к объекту через его переменные и поля других достижимых объектов, то браузер может безопасно освободить этот объект. Разница между этими двумя понятиями приводит к утечкам памяти, как показано в следующем примере.

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-тестирования новой функции для оценки ее влияния на использование памяти и выявления утечек памяти.
  • Сопоставление использования памяти с продолжительностью сеанса для проверки наличия или отсутствия утечек памяти.
  • Сопоставление использования памяти с пользовательскими показателями для понимания общего влияния использования памяти.

Совместимость с браузерами

Browser Support

  • Chrome: 89.
  • Край: 89.
  • Firefox: не поддерживается.
  • Safari: не поддерживается.

Source

В настоящее время 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 указывая на то, что они не смогли определить, какая из этих записей владеет памятью.

Обратная связь

Группа по повышению производительности веб-приложений и команда Chrome будут рады узнать ваше мнение и опыт использования performance.measureUserAgentSpecificMemory() .

Расскажите о проектировании API.

Есть ли что-то в API, что работает не так, как ожидалось? Или отсутствуют какие-то свойства, необходимые для реализации вашей идеи? Создайте заявку в репозитории GitHub `performance.measureUserAgentSpecificMemory()` или добавьте свои мысли к существующей заявке.

Сообщить о проблеме с реализацией

Вы обнаружили ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на new.crbug.com . Обязательно укажите как можно больше подробностей, предоставьте простые инструкции по воспроизведению ошибки и установите для параметра Components значение Blink>PerformanceAPIs .

Проявите поддержку

Планируете ли вы использовать performance.measureUserAgentSpecificMemory() ? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в разработке новых функций и показывает другим разработчикам браузеров, насколько важно их поддерживать. Отправьте твит @ChromiumDev и расскажите, где и как вы это используете.

Полезные ссылки

Благодарности

Большое спасибо Доменику Дениколе, Йоаву Вайсу и Матиасу Бюненсу за проверку дизайна API, а также Доминику Инфюру, Ханнесу Пайеру, Кентаро Харе и Майклу Липпаутцу за проверку кода в Chrome. Также благодарю Пера Паркера, Филиппа Вайса, Ольгу Беломестных, Мэтью Болохана и Нила Маккея за ценные отзывы пользователей, которые значительно улучшили API.