ดูวิธีวัดการใช้งานหน่วยความจำของหน้าเว็บในสภาพแวดล้อมการใช้งานจริงเพื่อตรวจหาการถดถอย
เบราว์เซอร์จะจัดการหน่วยความจำของหน้าเว็บโดยอัตโนมัติ เมื่อใดก็ตามที่หน้าเว็บสร้างออบเจ็กต์ เบราว์เซอร์จะจัดสรรหน่วยความจำส่วนหนึ่ง "เบื้องหลัง" เพื่อจัดเก็บออบเจ็กต์ เนื่องจากหน่วยความจำเป็นทรัพยากรที่มีจำกัด เบราว์เซอร์จึงทำการระบบจัดการหน่วยความจำที่ไม่ใช้แล้วเพื่อตรวจหาว่าเมื่อใดที่ออบเจ็กต์ไม่จำเป็นอีกต่อไปและเพื่อปล่อยหน่วยความจำส่วนที่เกี่ยวข้อง
อย่างไรก็ตาม การตรวจหาไม่ได้สมบูรณ์แบบ และปัญหาการหยุดทำงานของ Alan Turing ได้พิสูจน์แล้วว่าการตรวจหาที่สมบูรณ์แบบเป็นงานที่เป็นไปไม่ได้ ดังนั้นเบราว์เซอร์จึงประมาณแนวคิดที่ว่า "ออบเจ็กต์จำเป็น" ด้วยแนวคิดที่ว่า "ออบเจ็กต์เข้าถึงได้" หากหน้าเว็บเข้าถึงออบเจ็กต์ผ่านตัวแปรและช่องของออบเจ็กต์อื่นๆ ที่เข้าถึงได้ไม่ได้ เบราว์เซอร์จะเรียกคืนออบเจ็กต์ได้อย่างปลอดภัย ความแตกต่างระหว่างแนวคิดทั้งสองนี้ทำให้เกิดหน่วยความจำรั่วไหลดังที่แสดงในตัวอย่างต่อไปนี้
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
ในที่นี้ ระบบไม่จำเป็นต้องใช้อาร์เรย์ b ขนาดใหญ่อีกต่อไป แต่เบราว์เซอร์จะไม่
เรียกคืนเนื่องจากยังเข้าถึงได้ผ่าน object.b ในฟังก์ชันเรียกกลับ จึงทำให้หน่วยความจำของอาร์เรย์ขนาดใหญ่รั่วไหล
หน่วยความจำรั่วไหลเป็นปัญหาที่พบได้บ่อยบนเว็บ ดังที่เห็นได้จากการศึกษาครั้งนี้ การลืมยกเลิกการลงทะเบียน Listener เหตุการณ์ การจับออบเจ็กต์จาก iframe โดยไม่ได้ตั้งใจ การไม่ปิด Worker การสะสมออบเจ็กต์ในอาร์เรย์ และอื่นๆ เป็นสาเหตุที่ทำให้เกิดหน่วยความจำรั่วไหลได้ง่าย หากหน้าเว็บมีหน่วยความจำรั่วไหล การใช้งานหน่วยความจำจะเพิ่มขึ้นเมื่อเวลาผ่านไป และหน้าเว็บจะปรากฏช้าและมีขนาดใหญ่เกินไปสำหรับผู้ใช้
ขั้นตอนแรกในการแก้ปัญหานี้คือการวัดผล API
performance.measureUserAgentSpecificMemory() ใหม่ช่วยให้นักพัฒนาเว็บ
วัดการใช้งานหน่วยความจำของหน้าเว็บในสภาพแวดล้อมการใช้งานจริงได้ จึงตรวจพบหน่วยความจำ
รั่วไหลที่หลุดรอดจากการทดสอบในเครื่อง
performance.measureUserAgentSpecificMemory() แตกต่างจาก performance.memory API เดิมอย่างไร
หากคุณคุ้นเคยกับ performance.memory API ที่ไม่มีมาตรฐานซึ่งมีอยู่แล้ว คุณอาจสงสัยว่า API ใหม่นี้แตกต่างจาก API เดิมอย่างไร ความแตกต่างหลักคือ API เก่าจะแสดงขนาดของฮีป JavaScript ในขณะที่ API ใหม่จะประมาณหน่วยความจำที่หน้าเว็บใช้ ความแตกต่างนี้มีความสำคัญเมื่อ Chrome แชร์ฮีปเดียวกันกับหน้าเว็บหลายหน้า (หรือหน้าเว็บเดียวกันหลายอินสแตนซ์) ในกรณีดังกล่าว ผลลัพธ์ของ API เก่าอาจไม่ถูกต้องโดยสิ้นเชิง เนื่องจาก API เก่ากำหนดไว้ในข้อกำหนดเฉพาะของการใช้งาน เช่น "ฮีป" จึงเป็นไปไม่ได้ที่จะทำให้เป็นมาตรฐาน
ความแตกต่างอีกอย่างคือ API ใหม่จะวัดหน่วยความจำระหว่างระบบจัดการหน่วยความจำที่ไม่ใช้แล้ว ซึ่งจะช่วยลดสัญญาณรบกวนในผลลัพธ์ แต่ผลลัพธ์อาจใช้เวลาสักพักจึงจะปรากฏ โปรดทราบว่าเบราว์เซอร์อื่นๆ อาจตัดสินใจใช้ API ใหม่โดยไม่จำเป็นต้องอาศัยการเก็บขยะ
กรณีการใช้งานที่แนะนำ
การใช้งานหน่วยความจำของหน้าเว็บขึ้นอยู่กับเวลาที่เกิดเหตุการณ์ การดำเนินการของผู้ใช้ และการเก็บขยะ ด้วยเหตุนี้ Measurement API การวัดหน่วยความจำจึงมีไว้เพื่อรวบรวมข้อมูลการใช้งานหน่วยความจำจากสภาพแวดล้อมการใช้งานจริง ผลลัพธ์ของการเรียกแต่ละครั้งจึงมีประโยชน์น้อยกว่า ตัวอย่างกรณีการใช้งาน
- การตรวจหาการถดถอยระหว่างการเปิดตัวหน้าเว็บเวอร์ชันใหม่เพื่อตรวจหาหน่วยความจำรั่วไหลใหม่
- การทดสอบ A/B ของฟีเจอร์ใหม่เพื่อประเมินผลกระทบต่อหน่วยความจำและตรวจหาหน่วยความจำรั่วไหล
- การเชื่อมโยงการใช้งานหน่วยความจำกับระยะเวลาเซสชันเพื่อตรวจสอบว่ามีหรือไม่มีหน่วยความจำรั่วไหล
- การเชื่อมโยงการใช้งานหน่วยความจำกับเมตริกผู้ใช้เพื่อทำความเข้าใจผลกระทบโดยรวมของการใช้งานหน่วยความจำ
ความเข้ากันได้กับเบราว์เซอร์
ปัจจุบัน 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 จะไม่แสดงผลลัพธ์ Promise ในทันที แต่จะรอระบบจัดการหน่วยความจำที่ไม่ใช้แล้วครั้งถัดไป
การเรียก API จะบังคับให้มีการระบบจัดการหน่วยความจำที่ไม่ใช้แล้วหลังจากหมดเวลาที่กำหนด ซึ่งปัจจุบันตั้งไว้ที่ 20 วินาที แต่อาจเกิดขึ้นเร็วกว่านั้น การเริ่ม Chrome ด้วย
--enable-blink-features='ForceEagerMeasureMemory' Flag บรรทัดคำสั่งจะลด
การหมดเวลาเป็น 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() จะคำนวณช่วงเวลาแบบสุ่มเป็นมิลลิวินาทีเพื่อให้มีการวัดทุกๆ 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 และ Worker ที่ระบุโดย URL ช่อง types จะแสดงประเภทหน่วยความจำเฉพาะของการใช้งานที่เชื่อมโยงกับหน่วยความจำ
คุณควรจัดการรายการทั้งหมดในลักษณะทั่วไปและไม่ควรฮาร์ดโค้ดสมมติฐานตามเบราว์เซอร์ใดเบราว์เซอร์หนึ่ง ตัวอย่างเช่น เบราว์เซอร์บางเบราว์เซอร์อาจแสดง breakdown หรือ attribution ว่างเปล่า เบราว์เซอร์อื่นๆ อาจแสดงรายการหลายรายการใน attribution ซึ่งบ่งบอกว่าเบราว์เซอร์ไม่สามารถแยกแยะได้ว่ารายการใดเป็นเจ้าของหน่วยความจำ
ความคิดเห็น
กลุ่มชุมชนด้านประสิทธิภาพของเว็บและทีม Chrome อยากทราบ
ความคิดเห็นและประสบการณ์ของคุณเกี่ยวกับ
performance.measureUserAgentSpecificMemory()
แสดงความคิดเห็นเกี่ยวกับการออกแบบ API
มีบางอย่างเกี่ยวกับ API ที่ทำงานไม่เป็นไปตามที่คาดไว้ไหม หรือมีพร็อพเพอร์ตี้ที่ขาดหายไปซึ่งคุณต้องใช้เพื่อนำแนวคิดไปใช้ไหม โปรดแจ้งปัญหาเกี่ยวกับข้อกำหนดใน GitHub รีโป performance.measureUserAgentSpecificMemory() หรือเพิ่ม ความคิดเห็นของคุณในปัญหาที่มีอยู่
รายงานปัญหาเกี่ยวกับการใช้งาน
คุณพบข้อบกพร่องในการใช้งานของ Chrome ไหม หรือการใช้งานแตกต่างจากข้อกำหนดไหม โปรดแจ้งข้อบกพร่องที่ new.crbug.com อย่าลืม
ใส่รายละเอียดให้มากที่สุดเท่าที่จะทำได้ ให้คำแนะนำง่ายๆ ในการทำซ้ำ
ข้อบกพร่อง และตั้งค่า คอมโพเนนต์ เป็น Blink>PerformanceAPIs
แสดงการสนับสนุน
คุณวางแผนที่จะใช้ performance.measureUserAgentSpecificMemory() ไหม การสนับสนุนจากสาธารณะจะช่วยให้ทีม Chrome จัดลำดับความสำคัญของฟีเจอร์และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นๆ เห็นว่าการรองรับฟีเจอร์นี้มีความสำคัญเพียงใด ส่งทวีตไปที่ @ChromiumDev
และแจ้งให้เราทราบว่าคุณใช้ฟีเจอร์นี้ที่ใดและอย่างไร
ลิงก์ที่มีประโยชน์
- วิดีโออธิบาย
- เดโม | แหล่งที่มาของเดโม
- ข้อบกพร่องในการติดตาม
- รายการ ChromeStatus.com
- การเปลี่ยนแปลงตั้งแต่ API การทดสอบ Origin
- การทดสอบ Origin ที่สิ้นสุดแล้ว
คำขอบคุณ
ขอขอบคุณ 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 ได้อย่างมาก