ตรวจสอบการใช้หน่วยความจำทั้งหมดของหน้าเว็บด้วยmeasureUserAgentSpecificMemory()

ดูวิธีวัดการใช้หน่วยความจำของหน้าเว็บในเวอร์ชันที่ใช้งานจริงเพื่อตรวจหาการถดถอย

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

เบราว์เซอร์จะจัดการหน่วยความจำของหน้าเว็บโดยอัตโนมัติ เมื่อใดก็ตามที่หน้าเว็บสร้างออบเจ็กต์ เบราว์เซอร์จะจัดสรรหน่วยความจำส่วนหนึ่ง "ภายใน" เพื่อจัดเก็บออบเจ็กต์ เนื่องจากหน่วยความจำเป็นทรัพยากรที่มีจำกัด เบราว์เซอร์จึงจัดการขยะเพื่อตรวจจับเมื่อไม่จำเป็นต้องใช้ออบเจ็กต์อีกต่อไปและเพื่อเพิ่มพื้นที่ว่างในส่วนหน่วยความจำ

แต่การตรวจจับอาจไม่สมบูรณ์แบบ และได้รับการพิสูจน์แล้วว่าการตรวจจับที่สมบูรณ์แบบนั้นเป็นงานที่เป็นไปไม่ได้ ดังนั้นเบราว์เซอร์จะประมาณแนวคิดว่า "ต้องมีออบเจ็กต์" โดยมีแนวคิดว่า "เข้าถึงออบเจ็กต์ได้" หากหน้าเว็บเข้าถึงออบเจ็กต์ผ่านทางตัวแปรและช่องของออบเจ็กต์อื่นๆ ที่เข้าถึงได้ เบราว์เซอร์ก็จะเรียกคืนออบเจ็กต์นั้นกลับมาได้อย่างปลอดภัย ความแตกต่างระหว่างแนวความคิดทั้ง 2 แบบนี้นำไปสู่การรั่วไหลของหน่วยความจำดังที่แสดงในตัวอย่างต่อไปนี้

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

ในตัวอย่างนี้ คุณไม่จำเป็นต้องใช้อาร์เรย์ b ที่มีขนาดใหญ่กว่าอีกต่อไป แต่เบราว์เซอร์จะไม่อ้างสิทธิ์อีกครั้งเนื่องจากยังคงเข้าถึงได้ผ่าน object.b ใน Callback ดังนั้นหน่วยความจำของอาร์เรย์ที่ใหญ่กว่าจึงรั่วไหล

การรั่วไหลของหน่วยความจำเกิดขึ้นในเว็บ แนะนำได้ง่ายๆ ด้วยการลืมยกเลิกการลงทะเบียน Listener เหตุการณ์ โดยการจับภาพออบเจ็กต์จาก iframe โดยไม่ตั้งใจ โดยไม่ปิดผู้ปฏิบัติงาน ด้วยการสะสมออบเจ็กต์ในอาร์เรย์ และอื่นๆ หากหน่วยความจำรั่วไหลในหน้าเว็บ การใช้หน่วยความจำจะเพิ่มขึ้นเมื่อเวลาผ่านไป และหน้าเว็บจะแสดงช้าและมากเกินไปสำหรับผู้ใช้

ขั้นตอนแรกในการแก้ไขปัญหานี้คือการวัดค่า performance.measureUserAgentSpecificMemory() API ใหม่ช่วยให้นักพัฒนาซอฟต์แวร์วัดการใช้หน่วยความจำของหน้าเว็บในเวอร์ชันที่ใช้งานจริงได้ จึงตรวจจับการรั่วไหลของหน่วยความจำที่ผ่านการทดสอบในเครื่อง

performance.measureUserAgentSpecificMemory() แตกต่างจาก performance.memory API เดิมอย่างไร

หากคุณคุ้นเคยกับ performance.memory API ที่ไม่เป็นไปตามมาตรฐานที่มีอยู่แล้ว คุณอาจสงสัยว่า API ใหม่มีความแตกต่างจาก API นี้อย่างไร ความแตกต่างหลักๆ คือ API เก่าจะแสดงผลขนาดของฮีป JavaScript ขณะที่ API ใหม่จะประมาณหน่วยความจำที่หน้าเว็บใช้ ความแตกต่างนี้จะมีความสำคัญเมื่อ Chrome แชร์ฮีปเดียวกันกับหน้าเว็บหลายหน้า (หรืออินสแตนซ์หลายรายการของหน้าเว็บเดียวกัน) ในกรณีดังกล่าว ผลลัพธ์ของ API เก่าอาจปิดอยู่โดยไม่มีกฎเกณฑ์ เนื่องจาก API เดิมได้รับการกำหนดไว้ในคำศัพท์เฉพาะสำหรับการใช้งาน เช่น "Heap" ทำให้เป็นมาตรฐานที่ไม่หวังผล

ข้อแตกต่างอีกประการหนึ่งคือ API ใหม่จะทำการวัดหน่วยความจำในระหว่างการเก็บข้อมูลขยะ การดำเนินการดังกล่าวจะลดทอนผลลัพธ์ แต่อาจใช้เวลาสักระยะจนกว่าจะได้ผลลัพธ์ โปรดทราบว่าเบราว์เซอร์อื่นๆ อาจตัดสินใจใช้ API ใหม่โดยไม่อาศัยการเก็บขยะ

Use Case ที่แนะนำ

การใช้งานหน่วยความจำของหน้าเว็บขึ้นอยู่กับระยะเวลาของเหตุการณ์ การดำเนินการของผู้ใช้ และการดูแลจัดการขยะ นั่นเป็นเหตุผลที่ API การวัดหน่วยความจำมีไว้เพื่อรวบรวมข้อมูลการใช้งานหน่วยความจำจากเวอร์ชันที่ใช้งานจริง ผลของการโทรแต่ละครั้ง ไม่ค่อยมีประโยชน์ ตัวอย่างกรณีการใช้งาน

  • การตรวจจับการถดถอยระหว่างการเปิดตัวหน้าเว็บเวอร์ชันใหม่เพื่อตรวจจับการรั่วไหลของหน่วยความจำใหม่
  • การทดสอบ A/B ในฟีเจอร์ใหม่เพื่อประเมินผลกระทบของหน่วยความจำและตรวจจับการรั่วไหลของหน่วยความจำ
  • เชื่อมโยงการใช้งานหน่วยความจำกับระยะเวลาเซสชันเพื่อยืนยันว่ามีหรือไม่มีหน่วยความจำรั่วไหล
  • เชื่อมโยงการใช้งานหน่วยความจำกับเมตริกผู้ใช้เพื่อทำความเข้าใจผลกระทบโดยรวมของการใช้หน่วยความจำ

ความเข้ากันได้กับเบราว์เซอร์

การรองรับเบราว์เซอร์

  • 89
  • 89
  • x
  • x

แหล่งที่มา

ขณะนี้ 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' จะลดระยะหมดเวลาเป็น 0 และมีประโยชน์สำหรับการแก้ไขข้อบกพร่องและการทดสอบในเครื่อง

ตัวอย่าง

การใช้งานที่แนะนำของ 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() จะคำนวณช่วงเวลาแบบสุ่มเป็นมิลลิวินาที เช่น โดยเฉลี่ยแล้วจะมีการวัด 1 ครั้งทุก 5 นาที ดูการแจกแจงแบบเอ็กซ์โปเนนเชียลถ้าคุณสนใจคณิตศาสตร์ที่อยู่เบื้องหลังฟังก์ชัน

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, หน้าต่างที่เกี่ยวข้อง และ Web Worker ทั้งหมดในกระบวนการปัจจุบัน

รายการ breakdown ให้ข้อมูลเพิ่มเติมเกี่ยวกับหน่วยความจำที่ใช้ โดยแต่ละรายการจะอธิบายถึงหน่วยความจำบางส่วน และระบุว่าเป็นชุดหน้าต่าง, iframe และผู้ปฏิบัติงานที่ระบุโดย URL ช่อง types จะแสดงประเภทหน่วยความจำเฉพาะการใช้งานที่เชื่อมโยงกับหน่วยความจำ

สิ่งสำคัญคือจะต้องจัดการกับรายการทั้งหมดด้วยวิธีทั่วไป และไม่กำหนดให้มีการฮาร์ดโค้ดตามเบราว์เซอร์เฉพาะ เช่น บางเบราว์เซอร์อาจแสดง breakdown ที่ว่างเปล่าหรือ attribution ที่ว่างเปล่า เบราว์เซอร์อื่นอาจแสดงผลหลายรายการใน attribution ซึ่งระบุว่าแยกไม่ได้ว่ารายการใดเป็นเจ้าของหน่วยความจำ

ความคิดเห็น

กลุ่มชุมชนประสิทธิภาพเว็บและทีม Chrome อยากทราบ ความคิดเห็นและประสบการณ์ของคุณที่มีต่อ performance.measureUserAgentSpecificMemory()

บอกเราเกี่ยวกับการออกแบบ API

มีบางอย่างเกี่ยวกับ API ที่ไม่ทำงานตามที่คาดไว้หรือไม่ หรือขาดคุณสมบัติ ที่คุณต้องนำไอเดียไปปฏิบัติ แจ้งปัญหาเกี่ยวกับพื้นที่เก็บข้อมูล performance.measureUserAgentSpecificMemory() ของ GitHub หรือเพิ่มความคิดของคุณลงในปัญหาที่มีอยู่

รายงานปัญหาเกี่ยวกับการติดตั้งใช้งาน

คุณพบข้อบกพร่องในการติดตั้งใช้งาน Chrome ไหม หรือการใช้งานแตกต่างจากข้อกำหนด รายงานข้อบกพร่องที่ new.crbug.com อย่าลืมใส่รายละเอียดให้มากที่สุดเท่าที่จะทำได้ ให้คำแนะนำง่ายๆ สำหรับการสร้างข้อบกพร่องซ้ำ และตั้งค่าคอมโพเนนต์เป็น Blink>PerformanceAPIs ภาพ Glitch เหมาะสำหรับการแชร์ซ้ำที่ง่ายและรวดเร็ว

แสดงการสนับสนุน

คุณวางแผนที่จะใช้ performance.measureUserAgentSpecificMemory() ไหม การสนับสนุนสาธารณะของคุณจะช่วยให้ทีม Chrome จัดลำดับความสำคัญของฟีเจอร์ต่างๆ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นเห็นว่าการสนับสนุนเหล่านั้นมีความสำคัญเพียงใด ส่งทวีตไปที่ @ChromiumDev เพื่อแจ้งให้เราทราบถึงตำแหน่งและวิธีการใช้งานของคุณ

ลิงก์ที่มีประโยชน์

กิตติกรรมประกาศ

ขอขอบคุณ Domenic Denicola, Yoav Weiss, Mathias Bynens สำหรับรีวิวการออกแบบ API และ Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz สำหรับการตรวจสอบโค้ดใน Chrome ผมต้องขอขอบคุณ Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan และ Neil Mckay ที่ให้ความคิดเห็นอันมีค่าแก่ผู้ใช้ที่ช่วยปรับปรุง API ได้อย่างมาก

รูปภาพหลักโดย Harrison Broadbent ใน Unsplash