ตรวจสอบการใช้หน่วยความจำทั้งหมดของหน้าเว็บด้วย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 ในคอลแบ็ก ดังนั้นหน่วยความจำของอาร์เรย์ที่ใหญ่กว่าจึงรั่วไหล

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