איך למדוד את השימוש בזיכרון של דף האינטרנט בסביבת הייצור כדי לזהות רגרסיות.
הדפדפנים מנהלים את הזיכרון של דפי האינטרנט באופן אוטומטי. בכל פעם שדף אינטרנט יוצר אובייקט, הדפדפן מקצה חלק מהזיכרון "מאחורי הקלעים" כדי לאחסן את האובייקט. מכיוון שזיכרון הוא משאב מוגבל, הדפדפן מבצע איסוף אשפה כדי לזהות מתי אובייקט אינו נחוץ עוד ולפנות את נתח הזיכרון הבסיסי.
עם זאת, הזיהוי לא מושלם, ובעיית העצירה של אלן טיורינג הוכיחה שזיהוי מושלם הוא משימה בלתי אפשרית. לכן, דפדפנים משתמשים בקירוב של המושג "אובייקט נדרש" באמצעות המושג "אובייקט נגיש". אם דף האינטרנט לא יכול להגיע לאובייקט דרך המשתנים שלו והשדות של אובייקטים אחרים שניתן להגיע אליהם, הדפדפן יכול להחזיר את האובייקט באופן בטוח. ההבדל בין שני המושגים האלה מוביל לדליפות זיכרון, כמו בדוגמה הבאה.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
כאן המערך הגדול יותר b כבר לא נחוץ, אבל הדפדפן לא מחזיר אותו כי עדיין אפשר להגיע אליו דרך object.b בקריאה החוזרת (callback). כך, הזיכרון של המערך הגדול יותר דולף.
דליפות זיכרון נפוצות באינטרנט, כפי שאפשר לראות במחקר הזה. קל מאוד לגרום לדליפת זיכרון. למשל, אם שוכחים לבטל את הרישום של רכיב event listener, אם לוכדים בטעות אובייקטים מ-iframe, אם לא סוגרים worker, אם צוברים אובייקטים במערכים וכו'. אם יש דליפות זיכרון בדף אינטרנט, השימוש בזיכרון גדל עם הזמן, והדף נראה למשתמשים איטי ומנופח.
השלב הראשון בפתרון הבעיה הזו הוא למדוד אותה. performance.measureUserAgentSpecificMemory() API החדש מאפשר למפתחים למדוד את השימוש בזיכרון של דפי האינטרנט שלהם בסביבת הייצור, וכך לזהות דליפות זיכרון שלא אותרו בבדיקה המקומית.
מה ההבדל בין performance.measureUserAgentSpecificMemory() לבין ה-API הקודם של performance.memory?
אם אתם מכירים את ה-API הקיים הלא סטנדרטי של performance.memory, יכול להיות שאתם תוהים מה ההבדל בין ה-API החדש לבין ה-API הקיים. ההבדל העיקרי הוא שה-API הישן מחזיר את הגודל של ה-heap של JavaScript, ואילו ה-API החדש מעריך את הזיכרון שבו משתמש דף האינטרנט. ההבדל הזה חשוב כש-Chrome משתף את אותו מאגר עם כמה דפי אינטרנט (או כמה מופעים של אותו דף אינטרנט). במקרים כאלה, התוצאה של ה-API הישן עשויה להיות שגויה באופן שרירותי. מכיוון שה-API הישן מוגדר במונחים ספציפיים להטמעה כמו "heap", אין טעם לנסות להפוך אותו לתקני.
הבדל נוסף הוא שה-API החדש מבצע מדידת זיכרון במהלך איסוף אשפה. כך אפשר לצמצם את הרעש בתוצאות, אבל יכול להיות שייקח קצת זמן עד שהתוצאות יופקו. חשוב לדעת שדפדפנים אחרים עשויים להטמיע את ה-API החדש בלי להסתמך על איסוף אשפה.
תרחישים לדוגמה
השימוש בזיכרון של דף אינטרנט תלוי בתזמון של אירועים, פעולות של משתמשים ואיסוף אשפה. לכן, ה-API למדידת הזיכרון מיועד לצבירת נתוני שימוש בזיכרון בסביבת הייצור. התוצאות של שיחות בודדות פחות שימושיות. תרחישים לדוגמה:
- זיהוי רגרסיה במהלך השקה של גרסה חדשה של דף האינטרנט כדי לזהות דליפות זיכרון חדשות.
- בדיקת A/B של תכונה חדשה כדי להעריך את ההשפעה שלה על הזיכרון ולזהות דליפות זיכרון.
- התאמה בין השימוש בזיכרון לבין משך הסשן כדי לוודא אם יש או אין דליפות זיכרון.
- התאמה בין השימוש בזיכרון לבין מדדי המשתמשים כדי להבין את ההשפעה הכוללת של השימוש בזיכרון.
תאימות דפדפן
נכון לעכשיו, ה-API נתמך רק בדפדפנים שמבוססים על Chromium, החל מגרסה 89 של Chrome. התוצאה של ה-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' מקצרת את הזמן הקצוב לתפוגה לאפס, וזה שימושי לניפוי באגים ולבדיקות מקומיות.
דוגמה
השימוש המומלץ ב-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() מחשבת מרווח אקראי באלפיות השנייה, כך שבממוצע יש מדידה אחת כל חמש דקות. אם אתם מתעניינים במתמטיקה שמאחורי הפונקציה, קראו את המאמר התפלגות מעריכית.
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 Workers בתהליך הנוכחי.
הרשימה breakdown מספקת מידע נוסף על הזיכרון שבשימוש. כל רשומה מתארת חלק מהזיכרון ומשייכת אותו לקבוצה של חלונות, רכיבי iframe ו-workers שמזוהים לפי כתובת URL. בשדה types מפורטים סוגי הזיכרון הספציפיים להטמעה שמשויכים לזיכרון.
חשוב להתייחס לכל הרשימות באופן כללי ולא להטמיע בקוד הנחות שמבוססות על דפדפן מסוים. לדוגמה, דפדפנים מסוימים עשויים להחזיר breakdown ריק או attribution ריק. דפדפנים אחרים עשויים להחזיר כמה רשומות ב-attribution, מה שמצביע על כך שהם לא הצליחו להבחין איזו מהרשומות האלה היא הבעלים של הזיכרון.
משוב
הקבוצה Web Performance Community Group וצוות Chrome ישמחו לשמוע את דעתכם על performance.measureUserAgentSpecificMemory() ואת החוויות שלכם מהשימוש בו.
ספרו לנו על עיצוב ה-API
האם יש משהו ב-API שלא פועל כמו שציפית? או שחסרים מאפיינים שאתם צריכים כדי ליישם את הרעיון שלכם? מדווחים על בעיה במפרט במאגר performance.measureUserAgentSpecificMemory() ב-GitHub או מוסיפים את דעתכם לבעיה קיימת.
דיווח על בעיה בהטמעה
מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? מדווחים על הבאג בכתובת new.crbug.com. חשוב לכלול כמה שיותר פרטים, לספק הוראות פשוטות לשחזור הבאג ולהגדיר את Components לערך Blink>PerformanceAPIs.
הבעת תמיכה
יש לך כוונה להשתמש ב-performance.measureUserAgentSpecificMemory()? התמיכה הציבורית שלכם עוזרת לצוות Chrome לתעדף תכונות, ומראה לספקי דפדפנים אחרים עד כמה חשוב לתמוך בהן. שלחו ציוץ אל @ChromiumDev וספרו לנו איפה ואיך אתם משתמשים בו.
קישורים שימושיים
- הסבר
- באג מעקב
- רישום ב-ChromeStatus.com
- שינויים מאז גרסת המקור לניסיון של ה-API
- גרסת מקור לניסיון שהסתיימה
תודות
תודה רבה לדומניק דניקולה, יואב וייס ומתיאס בייננס על בדיקות העיצוב של ה-API, ולדומיניק אינפור, האנס פאייר, קנטארו הארה ומיכאל ליפאוץ על סקירות הקוד ב-Chrome. אני רוצה להודות גם ל-Per Parker, ל-Philipp Weis, ל-Olga Belomestnykh, ל-Matthew Bolohan ול-Neil Mckay על המשוב החשוב שסיפקו כמשתמשים, ששיפר מאוד את ה-API.