איך מודדים את השימוש בזיכרון של דף אינטרנט בסביבת ייצור כדי לזהות רגרסיות.
הדפדפנים מנהלים את הזיכרון של דפי אינטרנט באופן אוטומטי. בכל פעם שדף אינטרנט יוצר אובייקט, הדפדפן מקצה נתח זיכרון 'מתחת לפני השטח' כדי לאחסן את האובייקט. מכיוון שזיכרון הוא משאב מוגבל, הדפדפן מבצע איסוף אשפה כדי לזהות מתי אובייקט אינו נחוץ עוד ולפנות את נתח הזיכרון הבסיסי.
עם זאת, הזיהוי לא מושלם, ובעיית העצירה של אלן טיורינג מוכיחה שזיהוי מושלם הוא משימה בלתי אפשרית. לכן, הדפדפנים משתמשים במושג "אפשר להגיע לאובייקט" במקום במושג "נדרש אובייקט". אם דף האינטרנט לא יכול להגיע לאובייקט דרך המשתנים שלו והשדות של אובייקטים אחרים שאפשר להגיע אליהם, הדפדפן יכול לשחרר את האובייקט בבטחה. ההבדל בין שני המושגים האלה מוביל לדליפות זיכרון, כמו שמודגם בדוגמה הבאה.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
במקרה הזה, המערך הגדול יותר b כבר לא נחוץ, אבל הדפדפן לא משחרר אותו כי עדיין אפשר להגיע אליו דרך object.b בפונקציית הקריאה החוזרת. כך מתרחשת דליפת זיכרון של המערך הגדול יותר.
דליפות זיכרון נפוצות באינטרנט, כפי שאפשר לראות במחקר הזה. קל ליצור דליפת זיכרון, למשל אם שוכחים לבטל את הרישום של רכיב event listener, אם לוכדים בטעות אובייקטים מ-iframe, אם לא סוגרים worker, אם מצטברים אובייקטים במערכים וכו'. אם יש דף אינטרנט עם דליפות זיכרון, השימוש בזיכרון גדל לאורך זמן והדף נראה איטי ומנופח למשתמשים.
השלב הראשון בפתרון הבעיה הוא מדידה שלה. ה-API החדש של performance.measureUserAgentSpecificMemory() מאפשר למפתחים למדוד את השימוש בזיכרון של דפי האינטרנט שלהם בסביבת הייצור, וכך לזהות דליפות זיכרון שלא זוהו בבדיקות מקומיות.
מה ההבדל בין performance.measureUserAgentSpecificMemory() לבין performance.memory API מהדור הקודם?
אם אתם מכירים את performance.memory API הקיים שאינו סטנדרטי,
יכול להיות שאתם תוהים מה ההבדל בינו לבין API החדש. ההבדל העיקרי הוא שב-API הישן מוחזר הגודל של ערימת ה-JavaScript, ואילו ב-API החדש מוערך הזיכרון שבו נעשה שימוש בדף האינטרנט. ההבדל הזה חשוב כש-Chrome משתף את אותו heap עם כמה דפי אינטרנט (או עם כמה מופעים של אותו דף אינטרנט). במקרים כאלה, יכול להיות שהתוצאה של ה-API הישן תהיה שונה באופן שרירותי. מכיוון שה-API הישן מוגדר במונחים ספציפיים להטמעה, כמו 'heap', אין אפשרות לבצע סטנדרטיזציה שלו.
הבדל נוסף הוא שה-API החדש מבצע מדידת זיכרון במהלך איסוף אשפה. כך מצמצמים את הרעש בתוצאות, אבל יכול להיות שיעבור זמן עד שהתוצאות יתקבלו. שימו לב שדפדפנים אחרים עשויים להחליט להטמיע את ה-API החדש בלי להסתמך על מנגנון איסוף.
תרחישים לדוגמה
השימוש בזיכרון של דף אינטרנט תלוי בתזמון של אירועים, פעולות משתמשים ואיסוף נתונים לא רלוונטיים. לכן, ה-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);
}
בדיקה מקומית
כרום מבצע את מדידת הזיכרון במהלך איסוף הזבל, מה שאומר שה-API אינו פותר את הבטחת התוצאה באופן מיידי ובמקום זאת ממתין לאיסוף הזבל הבא.
קריאה ל-API גורמת למנגנון איסוף אחרי זמן קצוב לתפוגה מסוים, שמוגדר כרגע ל-20 שניות, אבל יכול לקרות מוקדם יותר. הפעלת Chrome עם התכונה הניסיונית בשורת הפקודה --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 worker בתהליך הנוכחי.
ברשימה breakdown מופיע מידע נוסף על הזיכרון שבשימוש. כל רשומה מתארת חלק מהזיכרון ומשייכת אותו לקבוצה של חלונות, רכיבי iframe ו-workers שמזוהים לפי כתובת URL. בשדה types מפורטים סוגי הזיכרון שספציפיים להטמעה ומשויכים לזיכרון.
חשוב להתייחס לכל הרשימות באופן כללי ולא לקודד הנחות מראש על סמך דפדפן מסוים. לדוגמה, דפדפנים מסוימים עשויים להחזיר breakdown ריק או attribution ריק. בדפדפנים אחרים יכול להיות שיוחזרו כמה רשומות ב-attribution, מה שמצביע על כך שלא הייתה אפשרות להבחין איזו מהרשומות האלה שייכת לזיכרון.
משוב
הקבוצה הקהילתית לביצועי אתרים וצוות Chrome ישמחו לשמוע את דעתכם על performance.measureUserAgentSpecificMemory() ולדעת מה היה הניסיון שלכם איתו.
נשמח לקבל מידע על עיצוב ה-API
האם יש משהו ב-API שלא פועל כמצופה? או שיש מאפיינים חסרים שצריך להטמיע כדי ליישם את הרעיון? אפשר לפתוח בקשה בקשר לבעיה במפרט במאגר GitHub של performance.measureUserAgentSpecificMemory() או להוסיף את המחשבות שלכם לבקשה קיימת.
דיווח על בעיה בהטמעה
מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? מדווחים על באג בכתובת new.crbug.com. חשוב לכלול כמה שיותר פרטים, לספק הוראות פשוטות לשחזור הבאג ולוודא שComponents מוגדר ל-Blink>PerformanceAPIs.
תמיכה ביוצרים
מתכננים להשתמש ב-performance.measureUserAgentSpecificMemory()? התמיכה הציבורית שלכם עוזרת לצוות Chrome לתעדף תכונות ומראה לספקי דפדפנים אחרים עד כמה חשוב לתמוך בהן. אתם יכולים לשלוח ציוץ אל @ChromiumDev ולספר לנו איפה ואיך אתם משתמשים בו.
קישורים שימושיים
- סרטון הסבר
- הדגמה | מקור ההדגמה
- באג במעקב
- רישום ב-ChromeStatus.com
- שינויים מאז Origin Trial API
- גרסת מקור לניסיון שהסתיימה
תודות
תודה רבה ל-Domenic Denicola, Yoav Weiss ו-Mathias Bynens על בדיקות עיצוב ה-API, ול-Dominik Inführ, Hannes Payer, Kentaro Hara ו-Michael Lippautz על בדיקות הקוד ב-Chrome. אני רוצה להודות גם לפר פרקר, פיליפ וייס, אולגה בלומסטניך, מתיו בולוהן וניל מקיי על המשוב החשוב שסיפקו, ששיפר מאוד את ה-API.