מעקב אחר השימוש הכולל בזיכרון בדף האינטרנט שלך באמצעותmeasureUserAgentspecificMemory()

במדריך הזה תלמדו איך למדוד את השימוש בזיכרון של דף האינטרנט בסביבת הייצור כדי לזהות רגרסיות.

ברנדן קני
ברנדן קני
אולן דגנבייב
אולן דגנבייב

דפדפנים מנהלים את הזיכרון של דפי האינטרנט באופן אוטומטי. בכל פעם שדף אינטרנט יוצר אובייקט, הדפדפן מקצה מקטע זיכרון "מאחורי הקלעים" כדי לאחסן את האובייקט. מכיוון שהזיכרון הוא משאב מוגבל, הדפדפן מבצע איסוף אשפה כדי לזהות מקרים שבהם אין יותר צורך באובייקט ולפנות את מקטע הזיכרון הבסיסי.

הזיהוי לא מושלם, והוכח שזיהוי מושלם הוא משימה בלתי אפשרית. לכן דפדפנים מנסחים את המושג "יש צורך באובייקט" עם המושג "אפשר להגיע לאובייקט". אם דף האינטרנט לא יכול להגיע לאובייקט דרך המשתנים שלו והשדות של אובייקטים אחרים שאפשר להגיע אליהם, הדפדפן יכול להחזיר את האובייקט בבטחה. ההבדל בין שני המושגים האלה מוביל לדליפות זיכרון, כפי שמתואר בדוגמה הבאה.

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

כאן כבר אין צורך במערך הגדול יותר b, אבל הדפדפן לא דורש אותו כי עדיין אפשר להגיע אליו דרך object.b בקריאה החוזרת (callback). כך הזיכרון של המערך הגדול יותר דולף.

דליפות זיכרון נפוצות באינטרנט. קל להציג אותו על ידי לשכוח לבטל את הרישום של event listener, על ידי לכידת אובייקטים מ-iframe בטעות, על-ידי אי סגירת worker, צבירת אובייקטים במערכים וכו'. אם יש דליפות זיכרון בדף אינטרנט, השימוש שלו בזיכרון גדל עם הזמן ודף האינטרנט נראה איטי ונפוח למשתמשים.

השלב הראשון בפתרון בעיה זו הוא מדידתה. ה-API החדש של performance.measureUserAgentSpecificMemory() מאפשר למפתחים למדוד את השימוש בזיכרון בדפי האינטרנט שלהם בסביבת הייצור, וכך לזהות דליפות זיכרון שחמקות מבדיקות מקומיות.

מה ההבדל בין performance.measureUserAgentSpecificMemory() לבין ה-API הקודם של performance.memory?

אם אתם מכירים את ה-API הלא סטנדרטי הקיים performance.memory, יכול להיות שתהיו מה ההבדל בין ה-API החדש. ההבדל העיקרי הוא שה-API הישן מחזיר את גודל ערימת ה-JavaScript ואילו ה-API החדש מעריכים את הזיכרון המשמש את דף האינטרנט. ההבדל הזה חשוב כש-Chrome משתף את אותה ערימה עם כמה דפי אינטרנט (או כמה מופעים של אותו דף אינטרנט). במקרים כאלה, התוצאה של ה-API הישן עשויה להיות מושבתת באופן שרירותי. מאחר שה-API הישן מוגדר במונחים ספציפיים להטמעה כמו 'heap', אז אין תקווה לתקינות.

הבדל נוסף הוא שה-API החדש מבצע מדידת זיכרון במהלך איסוף אשפה. הפעולה הזו מפחיתה את הרעש בתוצאות, אבל יכול להיות שייקח קצת זמן עד שהתוצאות ייווצרו. שימו לב שדפדפנים אחרים עשויים להחליט להטמיע את ה-API החדש בלי להסתמך על איסוף אשפה.

תרחישים לדוגמה

השימוש בזיכרון בדף אינטרנט תלוי בתזמון של האירועים, בפעולות המשתמש ובאיסוף האשפה. לכן ה-API של מדידת הזיכרון מיועד לצבירת נתוני שימוש בזיכרון מסביבת הייצור. התוצאות של שיחות ספציפיות פחות שימושיות. תרחישים לדוגמה:

  • זיהוי רגרסיה במהלך השקה של גרסה חדשה של דף האינטרנט כדי לזהות דליפות זיכרון חדשות.
  • בדיקת A/B של תכונה חדשה כדי להעריך את ההשפעה שלה על הזיכרון ולזהות דליפות זיכרון.
  • קישור בין השימוש בזיכרון לבין משך הסשן, כדי לאמת נוכחות של דליפות זיכרון או את הנוכחות שלהן.
  • קישור בין השימוש בזיכרון למדדי משתמשים כדי להבין את ההשפעה הכוללת של השימוש בזיכרון.

תאימות דפדפן

תמיכה בדפדפן

  • 89
  • 89
  • x
  • x

מקור

בשלב זה, ה-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 עם הדגל --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 של כל ה-iframes, חלונות קשורים ועובדי אינטרנט בתהליך הנוכחי.

ברשימה של breakdown יש מידע נוסף על הזיכרון שבשימוש. כל ערך מתאר חלק מהזיכרון ומייחס אותו לקבוצה של חלונות, מסגרות iframe ופועלים שמזוהים לפי כתובת URL. בשדה types מפורטים סוגי הזיכרון הספציפיים להטמעה שמשויכים לזיכרון.

חשוב להתייחס לכל הרשימות באופן כללי ולא להסקת מסקנות בתוך הקוד שמבוססות על דפדפן מסוים. לדוגמה, דפדפנים מסוימים עשויים להחזיר breakdown ריק או attribution ריק. דפדפנים אחרים עשויים להחזיר מספר רשומות ב-attribution כדי לציין שהם לא יכולים להבחין אילו מהרשומות האלה הוא הבעלים של הזיכרון.

משוב

קבוצת הקהילה של ביצועים באינטרנט והצוות של Chrome ישמחו לשמוע על דעתך ועל החוויות שלך עם performance.measureUserAgentSpecificMemory().

לספר לנו על עיצוב ה-API

האם יש משהו ב-API שלא פועל כצפוי? או האם חסרים מאפיינים שאתם צריכים כדי ליישם את הרעיון? שלחו בעיה במפרט במאגר Performance.measureUserAgentספציפיMemory() GitHub או הוסיפו את דעתכם לבעיה קיימת.

דיווח על בעיה בהטמעה

האם מצאת באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? אפשר לדווח על באג בכתובת new.crbug.com. חשוב לכלול כמה שיותר פרטים, לספק הוראות פשוטות לשחזור הבאג ולהגדיר את הרכיבים ל-Blink>PerformanceAPIs. גליץ' הוא כלי מעולה לשיתוף גיבויים מהירים וקלים.

הבעת תמיכה

יש לך כוונה להשתמש ב-performance.measureUserAgentSpecificMemory()? התמיכה הציבורית עוזרת לצוות של Chrome לתעדף תכונות, ומראה לספקי דפדפנים אחרים עד כמה קריטי התמיכה בהן. שלח ציוץ אל @ChromiumDev וספר לנו איפה אתם משתמשים בו ואיך אתם משתמשים בו.

קישורים שימושיים

אישורים

תודה גדולה לדמיניק דניקולה, יואב וייס, מתיאס ביינס על ביקורות על עיצוב API, ודומיניק אינפהר, האנס Payer, קנטארו הארה ומיכאל ליפוץ על ביקורות הקוד ב-Chrome. אני מודה גם לפר פארקר, לפיליפ ויס, אולגה בלומסטניך, מתיו בולוהאן וניל מקיי על שהם סיפקו משוב יקר ערך מהמשתמשים, ששיפר מאוד את ה-API.

תמונה ראשית (Hero) מאת Harison Broadbent ב-UnFlood