با measureUserAgentSpecificMemory()، کل مصرف حافظه صفحه وب خود را پایش کنید

با نحوه اندازه‌گیری میزان استفاده از حافظه صفحه وب در تولید برای شناسایی پس‌رفت‌ها آشنا شوید.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

مرورگرها حافظه صفحه‌های وب را به‌طور خودکار مدیریت می‌کنند. هرگاه صفحه وب شیئی ایجاد می‌کند، مرورگر بخشی از حافظه را «در پشت صحنه» برای ذخیره آن شیء اختصاص می‌دهد. ازآنجایی‌که حافظه منبعی محدود است، مرورگر زباله‌روبی انجام می‌دهد تا تشخیص دهد چه زمانی دیگر به شیء نیاز نیست و تکه حافظه زیربنایی را آزاد کند.

بااین‌حال، تشخیص بی‌نقص نیست و مسئله توقف آلن تورینگ ثابت کرد که تشخیص بی‌نقص کاری غیرممکن است. بنابراین مرورگرها مفهوم «یک شیء موردنیاز است» را با مفهوم «یک شیء قابل‌دسترسی است» تقریب می‌زنند. اگر صفحه وب نتواند ازطریق متغیرهایش و فیلدهای دیگر اشیای قابل‌دسترسی به شیء دسترسی پیدا کند، مرورگر می‌تواند با اطمینان شیء را بازیابی کند. تفاوت بین این دو مفهوم منجر به نشت حافظه می‌شود، همان‌طور که در مثال زیر نشان داده شده است.

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() چه تفاوتی با API قدیمی performance.memory دارد؟

اگر با performance.memory API غیر استاندارد موجود آشنا هستید، ممکن است برایتان سؤال باشد که API جدید چه تفاوتی با آن دارد. تفاوت اصلی این است که میانای برنامه‌سازی کاربردی قدیمی اندازه توده جاوا اسکریپت را برمی‌گرداند، درحالی‌که میانای برنامه‌سازی کاربردی جدید حافظه استفاده‌شده توسط صفحه وب را تخمین می‌زند. این تفاوت زمانی مهم می‌شود که Chrome توده یکسانی را با چند صفحه وب (یا چند نمونه از یک صفحه وب) هم‌رسانی می‌کند. در این موارد، نتیجه میانای برنامه‌سازی کاربردی قدیمی ممکن است به‌طور دلخواه خاموش باشد. ازآنجایی‌که میانای برنامه‌سازی کاربردی قدیمی در شرایط خاص پیاده‌سازی مانند «توده» تعریف شده است، استانداردسازی آن بی‌فایده است.

تفاوت دیگر این است که API جدید اندازه‌گیری حافظه را درطول جمع‌آوری زباله انجام می‌دهد. این کار نویز را در نتایج کاهش می‌دهد، اما ممکن است مدتی طول بکشد تا نتایج تولید شوند. توجه داشته باشید که مرورگرهای دیگر ممکن است تصمیم بگیرند API جدید را بدون تکیه بر جمع‌آوری زباله پیاده‌سازی کنند.

موارد استفاده پیشنهادی

استفاده از حافظه صفحه وب به زمان‌بندی رویدادها، کنش‌های کاربر، و جمع‌آوری زباله بستگی دارد. به همین دلیل است که میانای برنامه‌سازی کاربردی سنجش حافظه برای تجمیع داده‌های استفاده از حافظه از تولید درنظر گرفته شده است. نتایج تماس‌های فردی کمتر مفید است. نمونه موارد استفاده:

  • تشخیص رگرسیون درطول عرضه نسخه جدید صفحه وب برای شناسایی نشت‌های حافظه جدید.
  • آزمایش A/B ویژگی جدید برای ارزیابی تأثیر آن بر حافظه و شناسایی نشت حافظه.
  • مرتبط کردن استفاده از حافظه با مدت جلسه برای تأیید وجود یا عدم وجود نشت حافظه.
  • مرتبط کردن استفاده از حافظه با سنجه‌های کاربر برای درک تأثیر کلی استفاده از حافظه.

سازگاری مرورگر

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

درحال‌حاضر، این میانای برنامه‌سازی کاربردی فقط در مرورگرهای مبتنی بر Chromium، از 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 اندازه‌گیری حافظه را درطول جمع‌آوری زباله انجام می‌دهد، یعنی میانای برنامه‌سازی کاربردی نتیجه وعده را فوراً حل نمی‌کند و درعوض منتظر جمع‌آوری زباله بعدی می‌ماند.

فراخوانی میانای برنامه‌سازی کاربردی باعث می‌شود پس‌از مدتی وقفه، که درحال‌حاضر ۲۰ ثانیه تنظیم شده است، جمع‌آوری زباله انجام شود، هرچند ممکن است زودتر اتفاق بیفتد. شروع Chrome با پرچم خط فرمان --enable-blink-features='ForceEagerMeasureMemory' زمان اتمام را به صفر کاهش می‌دهد و برای اشکال‌زدایی و آزمایش محلی مفید است.

مثال

The recommended usage of the API is to define a global memory monitor that samples memory usage of the whole web page and sends the results to a server for aggregation and analysis. ساده‌ترین راه این است که به‌صورت دوره‌ای، مثلاً هر 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() میانای برنامه‌سازی کاربردی را فراخوانی می‌کند، نتیجه را ثبت می‌کند، و سنجش بعدی را زمان‌بندی می‌کند.

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 برگردانده می‌شود. این مقدار بسیار وابسته به پیاده‌سازی است و نمی‌توان آن را در مرورگرهای مختلف مقایسه کرد. حتی ممکن است بین نسخه‌های مختلف یک مرورگر تغییر کند. این مقدار شامل حافظه جاوا اسکریپت و DOM همه iframeها، پنجره‌های مرتبط، و وب‌ورکرها در فرایند فعلی است.

فهرست breakdown اطلاعات بیشتری درباره حافظه استفاده‌شده ارائه می‌دهد. هر ورودی بخشی از حافظه را توصیف می‌کند و آن را به مجموعه‌ای از پنجره‌ها، iframe، و workerهای شناسایی‌شده با نشانی وب نسبت می‌دهد. فیلد types انواع حافظه مختص پیاده‌سازی مرتبط با حافظه را فهرست می‌کند.

مهم است که با همه فهرست‌ها به‌صورت عمومی رفتار شود و فرض‌ها براساس مرورگر خاصی کدگذاری ثابت نشود. برای مثال، برخی‌از مرورگرها ممکن است breakdown خالی یا attribution خالی برگردانند. مرورگرهای دیگر ممکن است چند ورودی در attribution برگردانند که نشان می‌دهد نمی‌توانند تشخیص دهند کدام‌یک از این ورودی‌ها مالک حافظه است.

بازخورد

گروه انجمن عملکرد وب و تیم Chrome مایلند نظرات و تجربیات شما را درباره performance.measureUserAgentSpecificMemory() بشنوند.

درباره طراحی میانای برنامه‌سازی کاربردی به ما بگویید

آیا چیزی درباره API وجود دارد که طبق انتظار کار نمی‌کند؟ یا ویژگی‌هایی وجود دارد که برای پیاده‌سازی ایده‌تان به آن‌ها نیاز دارید؟ مشکل مشخصات را در مخزن GitHub performance.measureUserAgentSpecificMemory() ثبت کنید یا نظراتتان را به مشکل موجود اضافه کنید.

گزارش کردن مشکل در پیاده‌سازی

آیا اشکالی در پیاده‌سازی Chrome پیدا کرده‌اید؟ یا پیاده‌سازی با مشخصات متفاوت است؟ اشکالی را در new.crbug.com ثبت کنید. حتماً تا جایی که می‌توانید جزئیات ارائه دهید، دستورالعمل‌های ساده‌ای برای بازتولید اشکال ارائه دهید، و مؤلفه‌ها را روی Blink>PerformanceAPIs تنظیم کنید.

نمایش پشتیبانی

آیا قصد دارید از performance.measureUserAgentSpecificMemory() استفاده کنید؟ پشتیبانی عمومی شما به تیم Chrome کمک می‌کند ویژگی‌ها را اولویت‌بندی کند و به دیگر فروشندگان مرورگر نشان می‌دهد که پشتیبانی از آن‌ها چقدر حیاتی است. توییتی به @ChromiumDev ارسال کنید و به ما بگویید کجا و چگونه از آن استفاده می‌کنید.

پیوندهای مفید

تصدیق‌نامه‌ها

با تشکر فراوان از Domenic Denicola،‏ Yoav Weiss،‏ Mathias Bynens برای بازبینی‌های طراحی میانای برنامه‌سازی کاربردی، و Dominik Inführ،‏ Hannes Payer،‏ Kentaro Hara،‏ Michael Lippautz برای بازبینی‌های کد در Chrome. همچنین از پر پارکر، فیلیپ وایس، اولگا بلومستنیخ، متیو بولوهان، و نیل مک‌کی به‌خاطر ارائه بازخورد ارزشمند کاربران که API را بسیار بهبود بخشید سپاس‌گزارم.