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

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

Ulan Degenbaev
Ulan Degenbaev

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Chrome: 89
  • Edge: 89
  • Firefox: ไม่รองรับ
  • Safari: ไม่รองรับ

แหล่งที่มา

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

ตัวอย่าง

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

ตัวอย่างต่อไปนี้แสดงวิธีวัดหน่วยความจําแบบไม่ลำเอียงโดยใช้กระบวนการ Poisson ซึ่งรับประกันว่าตัวอย่างมีแนวโน้มที่จะเกิดขึ้นเท่าๆ กัน ณ เวลาใดก็ได้ (demo, source)

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

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

หากพบข้อบกพร่องในการใช้งาน 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