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

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

เบรนแดน เคนนี
Brendan Kenny
อูลาน เดเกนบาเยฟ
อูลาน เดเกินบาเยฟ

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

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

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

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

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

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

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

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

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

กรณีการใช้งานที่แนะนำ

การใช้งานหน่วยความจำของหน้าเว็บจะขึ้นอยู่กับระยะเวลาของเหตุการณ์ การดำเนินการของผู้ใช้ และการรวบรวมไฟล์ขยะ ด้วยเหตุนี้ 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 ทั้งหมด หน้าต่างที่เกี่ยวข้อง และผู้ปฏิบัติงานเกี่ยวกับเว็บในกระบวนการปัจจุบัน

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

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

ความคิดเห็น

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

บอกให้เราทราบเกี่ยวกับการออกแบบ API

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