تعرَّف على كيفية قياس استخدام الذاكرة في صفحة الويب في مرحلة الإنتاج لرصد حالات التراجع.
تدير المتصفّحات ذاكرة صفحات الويب تلقائيًا. عندما تنشئ صفحة ويب كائنًا، يخصّص المتصفّح جزءًا من الذاكرة "في الخلفية" لتخزين الكائن. بما أنّ الذاكرة هي مورد محدود، ينفّذ المتصفّح عملية جمع المهملات لرصد الحالات التي لا يعود فيها العنصر مطلوبًا وإخلاء مساحة الذاكرة الأساسية.
ومع ذلك، فإنّ عملية الرصد ليست مثالية، وقد أثبتت مسألة التوقف لآلان تورنغ أنّ الرصد المثالي هو مهمة مستحيلة. لذلك، تقرّب المتصفحات مفهوم "الحاجة إلى عنصر" بمفهوم "إمكانية الوصول إلى عنصر". إذا لم تتمكّن صفحة الويب من الوصول إلى عنصر من خلال متغيراته وحقول العناصر الأخرى التي يمكن الوصول إليها، يمكن للمتصفّح استعادة العنصر بأمان. يؤدي الاختلاف بين هذين المفهومين إلى تسرّب الذاكرة كما هو موضّح في المثال التالي.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
في هذه الحالة، لم تعد هناك حاجة إلى المصفوفة الأكبر b، ولكن المتصفّح لا يستردّها لأنّه لا يزال من الممكن الوصول إليها من خلال object.b في دالة الاستدعاء. وبالتالي، يتم تسريب ذاكرة المصفوفة الأكبر.
تنتشر مشكلة تسرُّب الذاكرة على الويب، كما يتضح من هذه الدراسة. من السهل حدوث تسرُّب للذاكرة عن طريق نسيان إلغاء تسجيل متتبِّع الأحداث، أو عن طريق التقاط عناصر عن طريق الخطأ من إطار iframe، أو عن طريق عدم إغلاق عامل، أو عن طريق تجميع العناصر في مصفوفات، وما إلى ذلك. إذا كانت صفحة الويب تتضمّن تسرّبات في الذاكرة، يزداد استخدامها للذاكرة بمرور الوقت وتظهر صفحة الويب بطيئة وممتلئة للمستخدمين.
الخطوة الأولى في حل هذه المشكلة هي قياسها. تتيح واجهة برمجة التطبيقات الجديدة
performance.measureUserAgentSpecificMemory() للمطوّرين
قياس استخدام الذاكرة لصفحات الويب في مرحلة الإنتاج، وبالتالي رصد تسرّب
الذاكرة الذي لا يتم رصده أثناء الاختبار المحلي.
ما هي أوجه الاختلاف بين performance.measureUserAgentSpecificMemory() وواجهة برمجة التطبيقات القديمة performance.memory؟
إذا كنت على دراية بواجهة برمجة التطبيقات performance.memory الحالية غير العادية،
قد تتساءل عن أوجه الاختلاف بين واجهة برمجة التطبيقات الجديدة والواجهة الحالية. يكمن الاختلاف الرئيسي في أنّ واجهة برمجة التطبيقات القديمة تعرض حجم كومة JavaScript، بينما تقدّر واجهة برمجة التطبيقات الجديدة حجم الذاكرة التي تستخدمها صفحة الويب. ويصبح هذا الاختلاف مهمًا عندما يشارك Chrome الذاكرة الديناميكية نفسها مع صفحات ويب متعددة (أو نُسخ متعددة من صفحة الويب نفسها). وفي هذه الحالات، قد تكون نتيجة واجهة برمجة التطبيقات القديمة غير دقيقة بشكل عشوائي. وبما أنّ واجهة برمجة التطبيقات القديمة محدّدة بمصطلحات خاصة بالتنفيذ مثل "كومة الذاكرة المؤقتة"، فإنّ توحيدها أمر مستحيل.
يتمثّل الاختلاف الآخر في أنّ واجهة برمجة التطبيقات الجديدة تقيس الذاكرة أثناء جمع المهملات. يؤدي ذلك إلى تقليل التشويش في النتائج، ولكن قد يستغرق الأمر بعض الوقت حتى يتم عرض النتائج. يُرجى العِلم أنّ المتصفّحات الأخرى قد تقرّر تنفيذ واجهة برمجة التطبيقات الجديدة بدون الاعتماد على ميزة "التعرّف على العناصر غير المستخدَمة".
حالات الاستخدام المقترَحة
يعتمد استخدام الذاكرة لصفحة ويب على توقيت الأحداث وإجراءات المستخدم وعمليات جمع المهملات. لهذا السبب، تهدف واجهة برمجة تطبيقات لقياس البيانات إلى تجميع بيانات استخدام الذاكرة من مرحلة الإنتاج. تكون نتائج المكالمات الفردية أقل فائدة. أمثلة على حالات الاستخدام:
- رصد الانحدار أثناء طرح إصدار جديد من صفحة الويب لرصد أي تسرُّب جديد للذاكرة
- إجراء اختبار A/B لميزة جديدة لتقييم تأثيرها في الذاكرة ورصد تسرُّب الذاكرة
- ربط استخدام الذاكرة بمدة الجلسة للتحقّق من وجود تسرّب الذاكرة أو عدمه
- ربط استخدام الذاكرة بمقاييس سلوك المستخدمين لفهم التأثير العام لاستخدام الذاكرة
توافُق المتصفح
في الوقت الحالي، تتوفّر واجهة برمجة التطبيقات فقط في المتصفّحات المستندة إلى Chromium، بدءًا من الإصدار 89 من Chrome. تعتمد نتيجة واجهة برمجة التطبيقات بشكل كبير على التنفيذ لأنّ المتصفّحات لديها طرق مختلفة لتمثيل العناصر في الذاكرة وطرق مختلفة لتقدير استخدام الذاكرة. قد تستبعد المتصفّحات بعض مناطق الذاكرة من الاحتساب إذا كان الاحتساب المناسب مكلفًا للغاية أو غير ممكن. وبالتالي، لا يمكن مقارنة النتائج عبر المتصفحات. من المفيد فقط مقارنة النتائج للمتصفح نفسه.
جارٍ استخدام 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 قياس الذاكرة أثناء تجميع المهملات، ما يعني أنّ واجهة برمجة التطبيقات لا تحلّ وعد النتيجة على الفور، بل تنتظر تجميع المهملات التالي.
يؤدي استدعاء واجهة برمجة التطبيقات إلى فرض تجميع المهملات بعد انتهاء مهلة معيّنة، والتي تم ضبطها حاليًا على 20 ثانية، على الرغم من أنّ ذلك قد يحدث في وقت أقرب. يؤدي بدء Chrome باستخدام علامة سطر الأوامر --enable-blink-features='ForceEagerMeasureMemory' إلى تقليل المهلة إلى الصفر، وهو أمر مفيد لتصحيح الأخطاء والاختبار على الجهاز.
مثال
الاستخدام المقترَح لواجهة برمجة التطبيقات هو تحديد أداة تتبُّع عامة للذاكرة
تأخذ عيّنات من استخدام الذاكرة لصفحة الويب بأكملها وترسل النتائج إلى خادم
لتجميعها وتحليلها. أبسط طريقة هي أخذ عينات بشكل دوري، على سبيل المثال كل 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. تعتمد هذه القيمة بشكل كبير على التنفيذ ولا يمكن مقارنتها على مستوى المتصفّحات. وقد يتغيّر حتى بين الإصدارات المختلفة من المتصفّح نفسه. تتضمّن القيمة ذاكرة JavaScript وDOM لجميع إطارات iframe والنوافذ ذات الصلة وعاملي الويب في العملية الحالية.
تقدّم قائمة breakdown مزيدًا من المعلومات حول الذاكرة المستخدَمة. يصف كل إدخال جزءًا من الذاكرة وينسبه إلى مجموعة من النوافذ وإطارات iframe وعمّال الخدمة التي يتم تحديدها من خلال عنوان URL. يسرد الحقل types أنواع الذاكرة الخاصة بالتنفيذ والمرتبطة بالذاكرة.
من المهم التعامل مع جميع القوائم بطريقة عامة وعدم ترميز
الافتراضات استنادًا إلى متصفّح معيّن. على سبيل المثال، قد تعرض بعض المتصفّحات
قيمة فارغة لـ breakdown أو attribution. قد تعرض المتصفّحات الأخرى إدخالات متعددة في attribution، ما يشير إلى أنّها لم تتمكّن من تحديد الإدخال الذي يملك الذاكرة.
الملاحظات
يسرّ مجموعة منتدى أداء الويب وفريق Chrome معرفة آرائك وتجاربك مع performance.measureUserAgentSpecificMemory().
أخبِرنا عن تصميم واجهة برمجة التطبيقات
هل هناك مشكلة في واجهة برمجة التطبيقات؟ أم أنّ هناك سمات ناقصة تحتاج إليها لتنفيذ فكرتك؟ يمكنك الإبلاغ عن مشكلة في المواصفات في مستودع performance.measureUserAgentSpecificMemory() على GitHub أو إضافة أفكارك إلى مشكلة حالية.
الإبلاغ عن مشكلة في عملية التنفيذ
هل عثرت على خطأ في تنفيذ Chrome؟ أم أنّ التنفيذ
مختلف عن المواصفات؟ يمكنك الإبلاغ عن الخطأ على new.crbug.com. تأكَّد من تضمين أكبر قدر ممكن من التفاصيل، وتقديم تعليمات بسيطة لإعادة إنتاج الخطأ، وضبط المكوّنات على Blink>PerformanceAPIs.
إظهار الدعم
هل تخطّط لاستخدام "performance.measureUserAgentSpecificMemory()"؟ يساعد دعمك العلني فريق Chrome في تحديد أولويات الميزات، كما يوضّح لمورّدي المتصفّحات الآخرين مدى أهمية إتاحة هذه الميزات. أرسِل تغريدة إلى @ChromiumDev
وأخبِرنا عن مكان وكيفية استخدامك لها.
روابط مفيدة
- فيديو توضيحي
- تتبُّع الأخطاء
- إدخال ChromeStatus.com
- التغييرات منذ إطلاق واجهة برمجة التطبيقات لمرحلة التجربة والتقييم
- مرحلة التجربة والتقييم المكتملة
الإقرارات
نتوجّه بالشكر إلى "دومينيك دينيكولا" و"يواف فايس" و"ماتياس باينينز" على مراجعة تصميم واجهة برمجة التطبيقات، وإلى "دومينيك إنفور" و"هانيس باير" و"كينتارو هارا" و"مايكل ليباوتز" على مراجعة الرموز البرمجية في Chrome. أشكر أيضًا "بير باركر" و"فيليب فايس" و"أولغا بيلوميستنيخ" و"ماثيو بولوخان" و"نيل مكاي" على تقديم ملاحظات قيّمة من المستخدمين ساهمت في تحسين واجهة برمجة التطبيقات بشكل كبير.