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

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

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

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

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

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

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

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

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

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

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

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

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

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

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

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

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

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

ตัวอย่าง

การใช้งาน 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() จะคำนวณช่วงเวลาแบบสุ่มเป็นมิลลิวินาทีเพื่อให้มีการวัดค่าโดยเฉลี่ย 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 ที่ไม่ทำงานตามที่คาดไว้หรือไม่ หรือมีพร็อพเพอร์ตี้ที่ขาดหายไปซึ่งคุณต้องใช้เพื่อนำแนวคิดไปใช้หรือไม่ แจ้งปัญหาเกี่ยวกับข้อกำหนดเฉพาะในที่เก็บ GitHub performance.measureUserAgentSpecificMemory() หรือเพิ่มความคิดเห็นของคุณลงในปัญหาที่มีอยู่

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

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

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

คุณวางแผนที่จะใช้ 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 ได้อย่างมาก