使用 measureUserAgent specificMemory() 監控網頁和#39 的記憶體總用量

瞭解如何評估正式環境中的網頁記憶體用量,以偵測迴歸問題。

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

瀏覽器會自動管理網頁的記憶體。每次網頁 瀏覽器會在「後端」配置一塊記憶體區塊到 儲存物件由於記憶體是有限資源,因此瀏覽器會 垃圾收集,偵測不再需要的物件 基準記憶體區塊

雖然偵測結果不盡完美, 簡直是不可能的任務因此,瀏覽器可估算出「物件」的概念 「這是必填項目」也就是「可以連上物件」的概念如果網頁無法 透過變數和其他可連線物件的欄位來存取物件 瀏覽器可以放心收回物件兩者之間的差異 兩個點子會導致記憶體流失,如以下範例所示。

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

這裡不再需要較大的陣列 b,但瀏覽器並不需要 卻仍可透過回呼中的 object.b 連上,因此能將其復原。因此 大型陣列的記憶體流失了

記憶體流失在網路上相當普遍。 引進一個簡單的作法,就是忘記註冊事件接聽程式,方法是 從 iframe 中意外擷取物件,方法是未關閉 worker, 還有陣列中的物件等。如果網頁記憶體不足 那麼記憶體用量會隨時間增加 而網頁顯示速度緩慢 都可能造成乾擾

要解決這個問題,第一步是評估成效。而 performance.measureUserAgentSpecificMemory() API 可讓開發人員 衡量網頁在正式作業環境中的記憶體用量,藉此偵測 或洩漏本機測試時的做法

performance.measureUserAgentSpecificMemory() 與舊版 performance.memory API 有何不同?

如果您已熟悉現有的非標準 performance.memory API, 您可能會好奇新 API 與目前之間的差異主要差別在於 舊版 API 會傳回 JavaScript 堆積的大小,而新的 API 則會估算網頁使用的記憶體這個差異會成為 Chrome 與多個網頁共用相同的堆積 ( 相同網頁的多個例項)。在這種情況下, API 可能會任意關閉由於舊版 API 的定義 「堆積」之類的實作術語,可以將資料標準化,並非如此。

另一個差異是,新的 API 會在 例如垃圾收集這樣可以減少結果中的雜訊 直到結果產生為止請注意,其他瀏覽器可能會決定 不必依賴垃圾收集機制

建議用途

網頁的記憶體用量取決於事件發生時間、使用者動作和 例如垃圾收集因此記憶體測量 API 的用途 匯總實際工作環境的記憶體使用資料。個別呼叫的結果 則較不實用應用情境範例:

  • 在新版本網頁推出期間進行迴歸偵測,找出新的記憶體流失問題。
  • 對新功能執行 A/B 測試,以評估其記憶體影響及偵測記憶體流失情形。
  • 將記憶體用量與工作階段持續時間相關聯,以驗證是否存在記憶體流失。
  • 找出記憶體用量與使用者指標之間的關係,藉此瞭解記憶體用量的整體影響。

瀏覽器相容性

瀏覽器支援

  • Chrome:89.
  • Edge:89。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

自 Chrome 第 89 版起,目前只有以 Chromium 為基礎的瀏覽器支援這個 API。 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 分鐘一次。但這樣會使資料產生偏誤 這些樣本可能會產生一些記憶體高峰

以下範例說明如何 使用 Poisson 處理程序進行無偏誤的記憶體測量。 可保證在任何時間點都有相同的樣本 (客層資料來源)。

首先,定義一個函式,用於排定下次記憶體測量作業 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 欄位中傳回預估記憶體總用量。此值為 與實作方面的高度相關,且無法跨瀏覽器進行比較。這可能會 甚至可以在同一個瀏覽器的不同版本間切換。這個值包括 所有 iframe、相關視窗及網路工作人員的 JavaScript 和 DOM 記憶體 目前的流程

breakdown 清單提供了已使用記憶體的進一步資訊。每項 項目會描述記憶體的某些部分,並註明為一組 以網址識別的視窗、iframe 和工作站。types 欄位會列出 與記憶體相關聯的實作專屬記憶體類型

請務必以通用方式處理所有清單,而不是以硬式編碼的方式處理 都會產生假設舉例來說,某些瀏覽器 會傳回空白的 breakdown 或空白的 attribution。其他瀏覽器 在 attribution 中傳回多個項目,表示兩者無法區分 哪些項目擁有記憶體

意見回饋

網頁成效社群群組和 Chrome 團隊會很開心 瞭解您對下列品牌的想法和經驗: performance.measureUserAgentSpecificMemory()

請與我們分享 API 設計

是否有 API 未正常運作?或者在那裡 缺少實現想法時所需的屬性?提交規格問題 performance.measureUserAgentSpecificMemory() GitHub 存放區或 對現有問題的看法

回報導入問題

您發現 Chrome 實作錯誤嗎?另一種是實作 該怎麼辦?前往 new.crbug.com 回報錯誤。請務必 盡可能附上更多細節,並提供重現重現說明的簡單說明 並將「Components」設為 Blink>PerformanceAPIsGlitch 有便捷的報復工具,

顯示支援

您是否打算使用「performance.measureUserAgentSpecificMemory()」?你的公開支援服務 可協助 Chrome 團隊優先開發功能,並向其他瀏覽器供應商說明 對他們來說至關重要將推文傳送到 @ChromiumDev ,並說明你使用這項服務的位置和方式。

實用連結

特別銘謝

非常感謝 Domenic Denicola、Yoav Weiss、Mathias Bynens 通過 API 設計審查。 和 Dominik Inführ、Hannes Payer、Kentaro Hara、Michael Lippautz 和 Michael Lippautz 評論程式碼 。我也感謝 Per Parker、Philipp Weis、Olga Belomestnykh 和 Matthew 的貢獻 Bolohan 和 Neil Mckay 為使用者提供了寶貴的使用者意見, 改善了 API

主頁橫幅Harrison Broadbent 提供,於 Unsplash 提供