یاد بگیرید که چگونه میزان استفاده از حافظه توسط صفحه وب خود را در محیط تولید اندازهگیری کنید تا بتوانید رگرسیونها را تشخیص دهید.
مرورگرها حافظه صفحات وب را به طور خودکار مدیریت میکنند. هر زمان که یک صفحه وب یک شیء ایجاد میکند، مرورگر بخشی از حافظه را "در زیر کاپوت" برای ذخیره شیء اختصاص میدهد. از آنجایی که حافظه یک منبع محدود است، مرورگر جمعآوری زباله را انجام میدهد تا تشخیص دهد چه زمانی دیگر به یک شیء نیازی نیست و بخش حافظه زیرین را آزاد کند.
با این حال، تشخیص بینقص نیست و مسئلهی توقف آلن تورینگ ثابت کرد که تشخیص بینقص، کاری غیرممکن است. بنابراین، مرورگرها مفهوم «یک شیء مورد نیاز است» را با مفهوم «یک شیء قابل دسترسی است» تقریب میزنند. اگر صفحه وب نتواند از طریق متغیرهای آن و فیلدهای سایر اشیاء قابل دسترسی به یک شیء دسترسی پیدا کند، مرورگر میتواند با خیال راحت شیء را پس بگیرد. تفاوت بین این دو مفهوم منجر به نشت حافظه میشود، همانطور که در مثال زیر نشان داده شده است.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
در اینجا دیگر به آرایه بزرگتر b نیازی نیست، اما مرورگر آن را پس نمیگیرد زیرا هنوز از طریق object.b در فراخوانی برگشتی قابل دسترسی است. بنابراین حافظه آرایه بزرگتر نشت میکند.
همانطور که از این مطالعه میتوان مشاهده کرد ، نشت حافظه در وب رایج است. با فراموش کردن لغو ثبت یک شنونده رویداد، با گرفتن تصادفی اشیاء از یک iframe، با نبستن یک worker، با انباشت اشیاء در آرایهها و غیره، میتوان به راحتی نشت حافظه ایجاد کرد. اگر یک صفحه وب نشت حافظه داشته باشد، میزان استفاده از حافظه آن به مرور زمان افزایش مییابد و صفحه وب برای کاربران کند و حجیم به نظر میرسد.
اولین قدم برای حل این مشکل، اندازهگیری آن است. API جدید performance.measureUserAgentSpecificMemory() به توسعهدهندگان اجازه میدهد تا میزان استفاده از حافظه صفحات وب خود را در محیط عملیاتی اندازهگیری کنند و در نتیجه نشت حافظهای را که از تستهای محلی عبور میکند، تشخیص دهند.
performance.measureUserAgentSpecificMemory() چه تفاوتی با API قدیمی performance.memory دارد؟
اگر با API غیراستاندارد performance.memory موجود آشنا هستید، ممکن است از خود بپرسید که API جدید چه تفاوتی با آن دارد. تفاوت اصلی این است که API قدیمی اندازه هیپ جاوا اسکریپت را برمیگرداند در حالی که API جدید حافظه استفاده شده توسط صفحه وب را تخمین میزند. این تفاوت زمانی اهمیت پیدا میکند که کروم هیپ یکسانی را با چندین صفحه وب (یا چندین نمونه از یک صفحه وب) به اشتراک بگذارد. در چنین مواردی، نتیجه API قدیمی ممکن است به طور دلخواه خاموش باشد. از آنجایی که API قدیمی با اصطلاحات خاص پیادهسازی مانند "هیپ" تعریف شده است، استانداردسازی آن بیفایده است.
تفاوت دیگر این است که API جدید در حین جمعآوری زباله، اندازهگیری حافظه را انجام میدهد. این امر باعث کاهش نویز در نتایج میشود، اما ممکن است مدتی طول بکشد تا نتایج تولید شوند. توجه داشته باشید که سایر مرورگرها ممکن است تصمیم بگیرند API جدید را بدون تکیه بر جمعآوری زباله پیادهسازی کنند.
موارد استفاده پیشنهادی
میزان استفاده از حافظه یک صفحه وب به زمانبندی رویدادها، اقدامات کاربر و جمعآوری زباله بستگی دارد. به همین دلیل است که API اندازهگیری حافظه برای جمعآوری دادههای استفاده از حافظه از محیط تولید در نظر گرفته شده است. نتایج فراخوانیهای فردی کمتر مفید هستند. موارد استفاده مثال:
- تشخیص رگرسیون در حین انتشار نسخه جدید صفحه وب برای شناسایی نشت حافظه جدید.
- تست A/B یک ویژگی جدید برای ارزیابی تأثیر آن بر حافظه و تشخیص نشت حافظه.
- همبستگی میزان استفاده از حافظه با مدت زمان جلسه برای تأیید وجود یا عدم وجود نشت حافظه.
- مرتبط کردن میزان استفاده از حافظه با معیارهای کاربر برای درک تأثیر کلی استفاده از حافظه.
سازگاری با مرورگرها
در حال حاضر، این API فقط در مرورگرهای مبتنی بر کرومیوم پشتیبانی میشود و از کروم ۸۹ شروع میشود. نتیجهی 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 ثانیه تنظیم شده است، جمعآوری زباله را اجباری میکند، هرچند ممکن است زودتر اتفاق بیفتد. شروع کروم با پرچم خط فرمان --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() به صورت async، رابط برنامهنویسی کاربردی (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 برگردانده میشود. این مقدار به شدت وابسته به پیادهسازی است و نمیتوان آن را در مرورگرهای مختلف مقایسه کرد. حتی ممکن است بین نسخههای مختلف یک مرورگر تغییر کند. این مقدار شامل حافظه جاوا اسکریپت و DOM مربوط به تمام iframeها، پنجرههای مرتبط و web workerها در فرآیند فعلی است.
فهرست breakdown اطلاعات بیشتری در مورد حافظه استفاده شده ارائه میدهد. هر ورودی بخشی از حافظه را توصیف میکند و آن را به مجموعهای از پنجرهها، iframeها و workerها که توسط URL شناسایی شدهاند، نسبت میدهد. فیلد types انواع حافظه مربوط به پیادهسازی مرتبط با حافظه را فهرست میکند.
مهم است که با همه لیستها به صورت عمومی رفتار شود و از فرضیات سختگیرانه بر اساس یک مرورگر خاص خودداری شود. به عنوان مثال، برخی از مرورگرها ممکن است یک breakdown خالی یا یک attribution خالی را برگردانند. برخی دیگر از مرورگرها ممکن است چندین ورودی را در attribution برگردانند که نشان میدهد نمیتوانند تشخیص دهند کدام یک از این ورودیها مالک حافظه هستند.
بازخورد
گروه انجمن عملکرد وب و تیم کروم دوست دارند نظرات و تجربیات شما را در مورد performance.measureUserAgentSpecificMemory() بشنوند.
در مورد طراحی API به ما بگویید
آیا چیزی در مورد API وجود دارد که طبق انتظار کار نمیکند؟ یا ویژگیهایی وجود ندارد که برای پیادهسازی ایده خود به آنها نیاز دارید؟ یک مشکل خاص را در مخزن GitHub با نام performance.measureUserAgentSpecificMemory() ثبت کنید یا نظرات خود را به یک مشکل موجود اضافه کنید.
گزارش مشکل در پیادهسازی
آیا در پیادهسازی کروم اشکالی پیدا کردید؟ یا پیادهسازی با مشخصات متفاوت است؟ یک اشکال را در new.crbug.com ثبت کنید. حتماً تا حد امکان جزئیات را ذکر کنید، دستورالعملهای سادهای برای تولید مجدد اشکال ارائه دهید و Components را روی Blink>PerformanceAPIs تنظیم کنید.
نمایش پشتیبانی
آیا قصد دارید از performance.measureUserAgentSpecificMemory() استفاده کنید؟ حمایت عمومی شما به تیم کروم کمک میکند تا ویژگیها را در اولویت قرار دهد و به سایر فروشندگان مرورگر نشان میدهد که پشتیبانی از آنها چقدر حیاتی است. یک توییت به @ChromiumDev ارسال کنید و به ما اطلاع دهید که کجا و چگونه از آن استفاده میکنید.
لینکهای مفید
- توضیح دهنده
- نسخه آزمایشی | منبع نسخه آزمایشی
- اشکال ردیابی
- ورودی ChromeStatus.com
- تغییرات از زمان انتشار نسخه آزمایشی رابط برنامهنویسی کاربردی (API) در Origin
- محاکمه مبدا به پایان رسید
تقدیرنامهها
با تشکر فراوان از Domenic Denicola، Yoav Weiss، Mathias Bynens برای بررسیهای طراحی API، و Dominik Inführ، Hannes Payer، Kentaro Hara، Michael Lippautz برای بررسی کد در Chrome. همچنین از Per Parker، Philipp Weis، Olga Belomestnykh، Matthew Bolohan و Neil Mckay برای ارائه بازخورد ارزشمند کاربران که API را تا حد زیادی بهبود بخشید، تشکر میکنم.