تتكون الأداة Performance من أربع أدوات جزئية سنشرحها في هذا المقال بشيء من التفصيل.
الأداة Waterfall
تقدِّم لك هذه الأداة عرضًا للأعمال التي ينفِّذها المتصفح عندما يستعرض موقعك أو تطبيقك، ويعتمد عمل الأداة على فكرة أنّ الأشياء التي ينفذها المتصفح عندما يستعرض موقعًا يمكن تصنيفها ضمن عدة فئات منها تشغيل شيفرة جافاسكربت وتحديث مخطط الصفحة وغيرها، وأنّ ما ينفذه المتصفح في لحظة ما هو شيء واحد فقط، فإذا لاحظت علامةً على مشكلة في الأداء مثل انخفاض عدد إطارات مع الزمن، فيمكنك التوجه إلى الأداة Waterfall لترى ما كان يفعله المتصفح في هذه اللحظة إذا كنت تسجل ملف الأداء.
يمثِّل المحور X الزمن المنقضي، كما تظهر العمليات المسجلة التي ندعوها علامات Markers على أساس أشرطة أفقية تتتابع مثل الشلال لتعكس الطبيعة التسلسلية لآلية تنفيذ المتصفح للعمليات، فعندما تختار علامةً ما، فسترى معلومات عنها ضمن الشريط الجانبي الذي يقع على اليمين وتتضمن الفترة الزمنية التي استغرقتها العلامة وغيرها من المعلومات الخاصة بنوع العلامة marker type.
العلامات
تملك العلامات التي ترتبط بالعمليات المنفذة نظامًا لونيًا محددًا وسنستعرضها في هذا القسم:
-
أحداث DOM: تمثَّل باللون، وهي شيفرة جافاسكربت التي تُنفَّذ على أساس استجابة لأحداث DOM. تقدم هذه العلامات معلومات عن ما يلي:
- نوع الحدث Event type: مثل نقرة click أو رسالة message.
- مرحلة الحدث Event Phase: مثل هدف Target أو التقاط Capture.
-
دوال جافاسكربت: تُمثَّل باللون، وهي الدوال التي ينفِّذها المتصفح في صفحة الويب وتُعنون وفقًا للسبب الذي استُدعي التابع لأجله، كما تقدِّم المعلومات على صورة مكدّس استدعاءات call stack مزوَّد بروابط إلى هذه الدوال:
- Script tag: تتعلق بالتعامل مع وسوم الشيفرة.
- setInterval: تتعلق بضبط الفترات الزمنية.
- setTimeout: تتعلق بتحديد زمن انتهاء العملية.
- requestAnimationFrame: تتعلق بطلبات إطار رسومي.
- Promise Callback: وتتعلق ياستدعاءات الوعود Promises.
- Promise Init: وتتعلق بتهيئة الوعود.
- Workers: تتعلق بعمّال ويب.
- JavaScript URI: تتعلق بعناوين موارد جافاسكربت.
- Event Handler: تتعلق بمعالجة الأحداث.
- تفسير HTML: تُمثَّل باللون وتمثِّل الوقت المستغرق في تفسير شيفرة HTML لصفحة الويب، كما تُقدِّم المعلومات على صورة مكدّس استدعاءات call stack مزوَّد بروابط إلى الدوال المستخدَمة.
- تفسير XML: تُمثَّل باللون وتمثِّل الوقت المستغرق في تفسير شيفرة XML لصفحة الويب، كما تُقدِّم المعلومات على صورة مكدّس استدعاءات مزوَّد بروابط إلى الدوال المستخدَمة.
-
إعادة ضبط التنسيق Recalculate Style: تُمثَّل باللون، وتعرض الحسابات اللازمة لضبط التنسيق المحدد لعناصر الصفحة، كما تقدِّم المعلومات على صورة تلميحات لإعادة التنسيق Restyle Hint، والتي هي قيم نصية تشير إلى نوع إعادة التنسيق الواجب تطبيقه وتأخذ القيم التالية:
- Self.
- Subtree.
- LaterSiblings.
- CSSTransitions.
- CSSAnimations.
- SVGAttrAnimations.
- StyleAttribute.
- StyleAttribute_Animations.
- Force.
- ForceDescendants.
- ضبط التخطيط Layout: تُمثَّل باللون وتعرض الحسابات اللازمة لتحديد موقع وحجم عناصر الصفحة، كما تدعى هذه العملية بإعادة الإنسياب reflow أحيانًا.
- رسم الصفحة Paint: تُمثَّل باللون وترسم البكسلات على الشاشة.
-
تجميع الموارد المستهلكة Garbage Collection تُمثَّل باللون وتعرض أحداث تجميع الموارد المستهلكة، ويُشار إلى أحداث تجميع الموارد المستهلكة اللاتصاعدي بالعبارة Non-incremental، كما تُقدِّم المعلومات على صورة قيم نصية وهي:
- "ReasonA": سلسلة نصية تدل على سبب استخدام مجمع الموارد المستهلكة GC.
- "Non-incremental Reason": سلسلة نصية تدل على سبب استخدام مجمع الموارد المستهلكة اللاتصاعدي.
- ستجد في فايرفوكس 46 ميزةً جديدةً هي تجميع الموارد المستهلكة، نظرًا لضغوطات ناتجة عن حجز الذاكرة، حيث يظهر في هذه الحالة رابطًا بعنوان عرض المسببات الناتجة عن حجز الذاكرة Show Allocation Triggers. انقر عندها على الرابط لتتابع ملف الأداء المتعلق بحجز الذاكرة، وصولًا إلى لحظة وقوع حدث تجميع الموارد المستهلكة.
- تدوير المستهلكات Cycle Collection: تُمثَّل باللون، وهي عملية تحرير بُنى البيانات التي تُخزِّن أرقام المراجع في اللغة ++C، وتشبه هذه العملية تجميع الموارد المستهلكة لكنها خاصة بكائنات ++C، وهذه العمليات دائمًا من النوع تجميع Collect.
- اختزال المنحني البياني لعملية تدوير المستهلكات CC Graph Reduction: تُمثَّل باللون، وتضم عمليتي التحضير أو التحسين الأولي لتدوير المستهلكات CC، وتكون هذه العمليات دائمًا من نوع تجاهل ما يمكن تجاوزه ForgetSkippable.
-
العلامة Console: تُمثَّل باللون، وهي الفترة الزمنية بين استدعائين مترابطين للدالتين
()console.time
و()console.timeEnd
، كما تُقدِّم المعلومات عبر:-
اسم المؤقت Timer name: على هيئة وسيط يمرَّر إلى الدالة
console
. -
المكدس في البداية stack at start: يعرض حالة مكدس الاستدعاء عند استدعاء الدالة
()console.time
مع روابط إلى بقية الدوال. -
المكدس في النهاية stack at end: وهو جديد في فايرفوكس 46، إذ يعرض حالة مكدس الاستدعاء عند استدعاء الدالة
()console.timeEnd
، فإذا كان الاستدعاء داخل وعدPromise
؛ فسيعرض أيضًا القيمة النصية "Async stack".
-
اسم المؤقت Timer name: على هيئة وسيط يمرَّر إلى الدالة
-
العلامة Timestamp: تُمثَّل باللون، وهي استدعاء مفرد للدالة
()console.timeStamp
، كما تُظهر الوسيط الذي مُرِّر إلى هذه الدالة. -
تحميل محتوى DOM: تُمثّل باللون، وتُظهر الحدث
DOMContentLoaded
. -
تحميل Load: تُمثّل باللون، وتظهر حدث تحميل المستند
load
. -
أحداث عمّال ويب في الخيط الأساسي Worker event in main thread: تُمثَّل باللون، وتظهر الحالات التي يرسل فيها الخيط الأساسي رسائل إلى عمّال الويب أو التي يستقبل فيها رسائل من العمّال، كما وتأخذ إحدى القيمتين:
- تهيئة البيانات على الخيط الرئيسي Serialize data on the main thread*، إذ يحوِّل الخيط الرئيسي الرسالة إلى صيغة مناسبة كي تُرسَل إلى العامل.
- تجميع البيانات على الخيط الرئيسي Deserialize data on the main thread: إذ يحوّل الخيط الرئيسي البيانات القادمة من العمال إلى بنية يمكن تخزينها أو عرضها.
-
أحداث عمال ويب في خيط العمال Worker event in worker thread: تُمثَّل باللون، وتظهر الحالات التي يرسَل فيها العامل رسائلًا إلى الخيط الرئيسي، أو التي يستقبل فيها رسائل من الخيط الرئيسي، كما تأخذ إحدى القيمتين:
- تهيئة البيانات للعامل Serialize data in worker: إذ يحوِّل العامل الرسالة إلى صيغة مناسبة كي تُرسَل إلى الخيط الرئيسي.
- تجميع البيانات عند العامل Deserialize data in worker: ويحوِّل العامل البيانات القادمة من الخيط الرئيسي إلى بنية يمكن للعامل فهمها.
تملك العلامات النمط اللوني نفسه في الأدلة Waterfall وفي لوحة النظرة العامة عندما تعرض هذه الأداة، مما يُسهِّل المطابقة بينهما.
ترشيح العلامات
يمكنك التحكم بالعلامات التي تريد استعراضها، وذلك من خلال زر الترشيح ضمن شريط الأدوات:
أنماط لعمليات تشير إليها الأداة Waterfall
يعتمد ما تعرضه لك الأداة Waterfall كثيرًا على نوع النشاط الذي يفعله موقعك، إذ ستبدو مواقع الويب التي تستخدِم جافاسكربت بشدة باللون البرتقالي، بينما سيظهر اللونين الأرجواني والأخضر بكثرة في المواقع التي تغير مظهرها المرئي بوتيرة مرتفعة، لكنك قد تلحظ بعض الأنماط التي تحذِّرك من مشاكل محتملة في الأداء.
تصيير التنفيذ المتسلل لعمل المتصفح
قد تعرض لك الأداة Waterfall النمط التالي:
يمثل الشكل السابق تصوّرًا للخوارزمية الأساسية التي يعتمدها المتصفح في الاستجابة لحدث:
- استدعاء دالة جافاسكربت: تتسبب بعض الأحداث في صفحة ويب مثل أحداث DOM، بتنفيذ شيفرة جافاسكربت، وقد تغيّر هذه الشيفرة بعض أجزاء شجرة DOM أو شجرة CSSOM.
- إعادة ضبط التنسيق: إذا لاحظ المتصفح تغيرات في التنسيق المحسوب لعناصر الصفحة، فلا بد من إعادة الحسابات وضبط التنسيق مجددًا.
- تخطيط الصفحة: يستخدِم المتصفح التنسيق المحسوب حديثًا في تقدير موقع وهندسة العناصر في الصفحة.، إذ تدعى هذه العملية بتخطيط الصفحة Layout، كما تدعى أحيانًا إعادة إنسياب reflow.
- رسم الصفحة: لا بد أخيرًا من إعادة رسم الصفحة على الشاشة، في حين لا تظهر الخطوة الأخيرة على الشاشة، وهي إمكانية تقسيم الصفحة إلى طبقات يُعاد رسمها بصورة مستقلة، ومن ثم يعاد دمجها بعملية التركيب Composition.
لا بد أن يتسع إطار واحد للتسلسل السابق طالما أنّ الشاشة لن تُحدَّث حتى اكتمال سلسلة التنفيذ السابقة، ومن المعروف أنّ معدل 60 إطار في الثانية كاف لإظهار الرسوميات المتحركة بسلاسة ونعومة على الشاشة، ويتطلب هذا من المتصفح تنفيذ السلسلة السابقة خلال 16.7 ميلي ثانية، كما من المهم معرفة من منظور الاستجابة أنّ المتصفح لا ينفِّذ بالضرورة كل خطوة من الخطوات السابقة:
- تُحدِّث رسوميات CSS الصفحة دون تنفيذ أيّ شيفرة جافاسكربت.
-
لا يعيد كل تغيير في خصائص تنسيق CSS تخطيط الصفحة، وإنما فقط تلك الخصائص التي تغيِّر موقع وهندسة العناصر، مثل
width
أوdisplay
أوfont-size
أوtop
؛ بينما لا تغير الخصائص مثلcolor
أوopacity
تخطيط الصفحة. -
لا يعيد كل تغيير في خصائص تنسيق CSS رسم الصفحة، فإذا حرّكت العنصر باستخدام خاصية التنسيق
transform
تحديدًا، فسيستخدِم المتصفح طبقةً منفصلةً للعنصر الذي طُبقت عليه تلك الخاصية، ولا حاجة حتى لإعادة الرسم عندما يتحرك العنصر، وإنما يُضبَط موقعه وهندسته أثناء عملية تركيب الطبقات.
سنطلع لاحقًا على تأثير رسوم CSS المتحركة على الأداء، وكيف تساعد الأداة Waterfall في الإشارة إلى ذلك.
حجب جافاسكربت
تُنفَّذ جافاسكربت افتراضيًا على الخيط نفسه الذي يستخدِمه المتصفح لتحديث تخطيط الصفحة وإعادة الرسم وتنفيذ أحداث DOM وغيرها، وهكذا فإنّ تنفيذ دوال جافاسكربت يستغرق وقتًا طويلًا قد يسبب توقف استجابة الصفحة وخشونةً في عرض الرسوميات، كما قد تتجمد الصفحة بالكامل، في حين يمكن بسهولة رصد الأوقات التي تسبب فيها شيفرة جافاسكربت مشاكلًا في الاستجابة باستخدام الأداتين Frame rate وWaterfall معًا. لاحظ كيف حددنا في الشكل التالي منطقةً سببت فيه دالة جافاسكربت نقصًا في معدل الإطارات.
سنطّلع في مقال تأثير الاستخدام المكثف لجافاسكربت على الأداء على أهمية الأداة Waterfall في تحديد مشاكل الاستجابة التي تسببها دوال جافاسكربت وكيفية استخدام التوابع غير المتزامنة للمحافظة على استجابة خيط التنفيذ.
عملية رسم عناصر مستنزفة للوقت
قد تستنزف بعض تأثيرات رسم العناصر وقتًا طويلًا مثل box-shadow
، وخاصةً عند تطبيق هذه التأثيرات في حالات تحريك العناصر، إذ ينبغي على المتصفح إعادة ضبط هذه التأثيرات مع كل إطار، فإذا لاحظت انخفاضًا في معدل الإطارات عند تنفيذ عمليات رسومية كثيفة أو عمليات تحريك للعناصر خصوصًا، فتحقق من العلامات الخضراء في الأداة Waterfall.
تجميع الموارد المستهلكة
تشير العلامات الحمراء في الأداة Waterfall إلى عمليات تجميع الموارد المستهلكة GC -بغية تحرير الذاكرة المرتبطة بها-، إذ يتجول محرك جافاسكربت والذي يُدعى SpiderMonkey في فايرفوكس في كومة الذاكرة عن المناطق التي لم يَعُد بالإمكان الوصول إليها وتحريرها، وتؤثر هذه العملية في الأداء لأنه لا بد من إيقاف محرك جافاسكربت عن تنفيذ الشيفرة مؤقتًا عندما تبدأ عملية تجميع الموارد المستهلكة، وبالتالي سيُعلَّق تنفيذ برنامجك وقد لا يستجيب إطلاقًا.
ينفذ المحرك SpiderMonkey ما يسمى التجميع المتصاعد للموارد المستهلكة incremental GC لتخفيف هذه المشكلة في فايرفوكس، إذ تقتضي هذه العملية تنفيذ عملية تجميع الموارد المستهلكة على دفعات متصاعدة صغيرة تلائم تنفيذ البرنامج خلالها، لكن لا بد في بعض الحالات من تنفيذ عملية تجميع موارد مستهلكة لا تصاعدي، وبالتالي على البرنامج الانتظار حتى نهاية العملية، كما من الأفضل عدم محاولة تخصيص طريقة لتفادي عملية تجميع موارد مستهلكة لمحرك جافاسكربت معيّن وخاصةً أحداث تجميع الموارد المستهلكة التصاعدية، إذ يستخدِم SpiderMonkey مثلًا مجموعةً معقدةً من الطرق الاستدلالية لتقدير الحاجة إلى استخدام عملية تجميع الموارد المستهلكة اللاتصاعدية والتصاعدية خصوصًا، ويمكن القول أنه:
- نحتاج إلى تجميع الموارد المستهلكة عندما تُحجَز مساحات كبيرة من الذاكرة.
- نحتاج إلى تجميع الموارد تصاعديًا عندما يكون معدل حجز الذاكرة مرتفعًا إلى الحد الذي تستنزف فيه ذاكرة محرك جافاسكربت عند تنفيذ التجميع اللاتصاعدي.
عندما تسجل الأداة Waterfall علامة تجميع موارد مستهلكة، فهي تشير إلى:
- العملية تصاعدية أم لا.
- سبب تنفيذ العملية.
- سبب تنفيذ العملية اللاتصاعدية إذا كانت كذلك.
- ستجد في فايرفوكس 46 وما بعد ميزةً جديدةً هي تجميع الموارد المستهلكة على أساس نتيجة لضغوطات ناتجة عن حجز الذاكرة، حيث يظهر في هذه الحالة رابطًا بعنوان عرض المسببات الناتجة عن حجز الذاكرة Show Allocation Triggers، وانقر عندها على الرابط لتتابع ملف الأداء المتعلق بحجز الذاكرة، وصولًا إلى لحظة وقوع حدث تجميع الموارد المستهلكة، كما سنعرض هذه الموضوع لاحقًا بشيء من التفصيل.
إضافة العلامات من خلال الواجهة البرمجية للطرفية
يمكن التحكم بعلامتين فقط من خلال الواجهة البرمجية للطرفية Console API هما: "Console" و"Timestamp".
علامات Console
تساعدك على تحديد قسم محدد من التسجيل، وبالتالي استدع الدالة ()console.time
في بداية القسم الذي تريد تحديده والدالة ()console.timeEnd
عند نهايته، كما تقبل هاتين الدالتين وسيطًا يُستخدَم في تسمية هذا القسم، ولنفرض مثلًا أننا أمام الشيفرة التالية:
var iterations = 70; var multiplier = 1000000000; function calculatePrimes() { console.time("calculating..."); var primes = []; for (var i = 0; i < iterations; i++) { var candidate = i * (multiplier * Math.random()); var isPrime = true; for (var c = 2; c <= Math.sqrt(candidate); ++c) { if (candidate % c === 0) { // not prime isPrime = false; break; } } if (isPrime) { primes.push(candidate); } } console.timeEnd("calculating..."); return primes; }
سيبدو خرج الأداة waterfall كما يلي:
تُحدَّد العلامة من خلال الوسيط الذي مُرِّر إلى الدالة ()console.time
، كما يمكنك متابعة مكدس البرنامج ضمن الشريط الجانبي إلى اليمين عندما تختار هذه العلامة.
أما بخصوص مكدس العملية اللامتزامنة Async stack، فسيعرِض الشريط الجانبي ابتداءً من النسخة فايرفوكس 41 حالة المكدس في نهاية التسجيل عند استدعاء ()console.timeEnd
، فإذا استدعيت ()console.timeEnd
من داخل وعد Promise
، فسيعرض الشريط العبارة "(Async: Promise)" وسترى تحتها العبارة "async stack" التي تدل على حالة مكدس الاستدعاء في لحظة تنفيذ الوعد، وتأمل الشيفرة التالية على سبيل المثال:
var timerButton = document.getElementById("timer"); timerButton.addEventListener("click", handleClick, false); function handleClick() { console.time("timer"); runTimer(1000).then(timerFinished); } function timerFinished() { console.timeEnd("timer"); console.log("ready!"); } function runTimer(t) { return new Promise(function(resolve) { setTimeout(resolve, t); }); }
وستعرض الأداة Waterfall علامةً للفترة الزمنية المنقضية بين الاستدعائين ()time
و()timeEnd
، فإذا نقرت عليها، فستشاهد المكدس اللامتزامن ضمن الشريط الجانبي:
علامات Timestamp
تساعدك هذه العلامة على تحديد لحظة زمنية ضمن التسجيل، لذا استدع الدالة ()console.timeStamp
لتضع علامة timestamp، كما يمكنك تمرير اسم لهذه العلامة من خلال تمرير الاسم على أساس وسيط للدالة السابقة، ولنفترض أننا عدلنا الشيفرة التي عرضناها سابقًا لتحديد لحظة زمنية كل 10 تكرارات للحلقة على سبيل المثال، ونختار عدد التكرارات على أساس اسم لهذه اللحظة:
var iterations = 70; var multiplier = 1000000000; function calculatePrimes() { console.time("calculating..."); var primes = []; for (var i = 0; i < iterations; i++) { if (i % 10 == 0) { console.timeStamp(i.toString()); } var candidate = i * (multiplier * Math.random()); var isPrime = true; for (var c = 2; c <= Math.sqrt(candidate); ++c) { if (candidate % c === 0) { // not prime isPrime = false; break; } } if (isPrime) { primes.push(candidate); } } console.timeEnd("calculating..."); return primes; }
ستعرض الأداة waterfall شيئًا من هذا القبيل:
الأداة Frame rate
يُعَدّ معدّل الإطارات مقياسًا لاستجابة الصفحة، وقد تتسم الصفحات التي تتمتع بمعدل إطارات ضعيف أو غير مستقر بضعف الاستجابة أو الجمود، مما يؤثر على تجربة المستخدِم.
اقتباستوضيح: يقدِّم معدل 60 إطار في الثانية أداءً سلسًا، كما يتيح فترةً زمنيةً مقدارها 16.7 ميلي ثانية لإنجاز كل التحديثات المطلوبة للاستجابة إلى بعض الأحداث.
يتيح الرسم البياني لمعدل الإطارات في الأداة Performance إمكانية إظهار تغير معدل الإطارات خلال فترة التسجيل، ويدلك بسرعة إذا كانت الصفحة ستعاني من مشاكل في الأداء وتمكِّنك من استخدام بقية الأدوات الجزئية بفعالية أكبر لتحليل المشكلة.
علاقة معدل الإطارات باستجابة الصفحة
يُعرَّف معدل الإطارات تقليديًا بأنه معدّل التقاط الصور أو الإطارات بواسطة جهاز فيديو، وهو مألوف جدًا في عالم التصوير والألعاب، لكنه يستخدَم حاليًا على نطاق واسع في قياس أداء موقع ويب أو تطبيق ويب، حيث يمثِّل الإطار -عند تقييم الأداء في ويب- العمل الذي ينبغي على المتصفح تنفيذه لتحديث وإعادة رسم الشاشة، كما تُعَدّ الرسوم المتحركة النواحي الأكثر التي يُطبق فيها هذا المقياس، فإذا كان المعدل منخفضًا جدًا، فستكون استجابة الرسوم المتحركة بطيئةً وخشنةً، في حين سيكون عرض الرسوم أكثر سلاسةً عندما يكون معدل الإطارات أسرع، ومع ذلك يُعَدّ معدل الإطارات مفيدًا في قياس مدى استجابة المواقع عندما يتفاعل معها المستخدِم.
إذا أدى تمرير مؤشر الفأرة على سبيل المثال فوق أحد عناصر صفحة ويب إلى وقوع حدث من أحداث جافاسكربت يغير مظهر ذلك العنصر، فسيؤدي ذلك إلى تنفيذ عمليتَي إعادة تخطيط العناصر ضمن الصفحة، ثم إعادة رسم الشاشة، ولا بد من اكتمال هذه العمليات جميعها في إطار واحد، فإذا استغرق أمر معالجة هذا الإطار من قِبَل المتصفح وقتًا طويلًا، فستتوقف استجابة المتصفح مؤقتًا، وكذلك الأمر عندما تتنقل ضمن صفحة تتطلب العديد من عمليات التحديث المعقدة بحيث لا يتمكن المتصفح من المحافظة على معدل إطارات مقبول، وهنا ستلاحظ بطأً في تمرير الصفحة صعودًا أو نزولًا وقد تتجمد أثناء ذلك، فعندما يكون معدل الإطارات مرتفعًا وثابتًا عمومًا، فسيريح ذلك المستخدِم ويجعل تجربته أكثر متعةً.
يقدِّم معدل 60 إطار في الثانية أداءً سلسلًا ويتيح فترةً زمنيةً مقدارها 16.7 ميلي ثانية لإنجاز كل التحديثات المطلوبة، وذلك بالتزامن مع الاستجابة إلى بعض الأحداث، ولا بد من الإشارة إلى الأهمية الخاصة للاستمرارية أو ثبات معدل الإطارات، فإذا لم تستطع تحقيق 60 إطار في الثانية، فحاول تحقيق معدل أقل لكن على فترات زمنية طويلة -أي الاستمرارية-، وتفادي أيّ انخفاض مفاجئ قد يسبب جمود الصفحة.
المخطط البياني لمعدل الإطارات
ستجد هذا المخطط البياني في قسم النظرة العامة للتسجيل ضمن الأداة Performance، حيث يلتقط المخطط اللحظة الزمنية التي ينهي فيها المتصفح إطارًا، ويستخدِمها ليتتبع معدل الإطارات أثناء فترة التسجيل.
يمثِّل المحور X الزمن المنقضي أثناء تسجيل ملف الأداء، كما ستلاحظ وجود ثلاث مؤشرات هي معدل الإطارات الأعظمي ومعدل الإطارات الوسطي ومعدل الإطارات الأقل.
طريقة استخدام المخطط البياني لمعدل الإطارات
تدل قيمة معدل الإطارات على بعض المشاكل المحتملة التي قد تواجهها الصفحة، وبالتالي سيساعدك ذلك في استخدام أدوات أخرى لتحليل مشاكل الأداء بعمق أكثر، وإليك على سبيل المثال لقطة الشاشة التالية لملف أداء:
ستجد أنّ معدل الإطارات الوسطي جيد، لكنك ستلاحظ أيضًا ثلاث لحظات ينهار فيها معدل الإطارات خلال أعشار من الميلي ثانية، إذ يسبب ذلك دون أي شك تعثرًا ملحوظًا في عرض الرسوم المتحركة ضمن الصفحة، كما يرتبط معدل الإطارات مع عرض مختصر لعلامات الأداة waterfall فوقه مباشرةً، ولاحظ كيف ترافق أول انهيارين في معدل الإطارات في اللقطة السابقة مع شريط برتقالي يدل على الوقت المستغرَق في تنفيذ شيفرة جافاسكربت، فإذا اخترنا شريحةً تضم إحدى هاتين اللحظتين، فسيعرض قسم التفاصيل في الأسفل تفاصيل هذه الشريحة، وسنتابع الدالة التي سببت المشكلة:
لاحظ كيف أعاقت دالة جافاسكربت استُدعيت عند حدث النقر خيط التنفيذ الرئيسي عن العمل مدة 170 ميلي ثانية، ولمعرفة هذه الدالة تحديدًا انتقل إلى الأداة Flame Chart لمتابعة مكدّس الاستدعاءات في هذه النقطة:
هذه الدالة إذًا ()doPointlessComputations
وهي دالة معرفة في الملف "main.js"، وقد نضطر لحل المشكلة إلى تقسيم الدالة إلى أجزاء، وتنفيذ كل جزء على حدة ضمن requestAnimationFrame
أو تنفيذها بالكامل ضمن عامل ويب، وسنتابع لاحقًا حلولًا لمشاكل مثل هذه عندما نحلل تأثير الاستخدام المكثف لجافاسكربت على الأداء.
الأداة Call Tree
تساعدك هذه الأداة على تحديد دوال جافاسكربت التي يقضي المتصفح الوقت الأطول في تنفيذها، كما تساعدك عند تحليل النتائج التي تعرضها على إيجاد الاختناقات في شيفرتك، أي الأماكن التي يقضي فيها المتصفح وقتًا طويلًا بلا مبرر، إذ تُعَدّ نقاط الاختناق هذه هي الهدف الأفضل للتحسينات التي يمكن إجراؤها على الشيفرة والأكثر تأثيرًا.
تُعَدّ الأداة Call Tree أداةً لتجميع العينات، إذ تلتقط دوريًا عينات لتحديد حالة محرك جافاسكربت وتسجيل حالة المكدّس في زمن تنفيذ الشيفرة، في حين يتعلق عدد العينات المجمّعة إحصائيًا عند تنفيذ دالة محددة بالزمن الذي يستغرقه المتصفح في تنفيذها، ولشرح فائدة الأداة Call Tree، سنستخدم خرج برنامج بسيط على أساس مثال، فإذا أردت الحصول على هذا البرنامج لتجربه بنفسك، فيمكنك تنزيله من المستودع الخاص به على جيت هاب، كما يمكنك تنزيل ملف الأداء الذي سنشرحه من المستودع ذاته وإدراجه بعد ذلك ضمن الأداة Performance، وستجد كذلك شرحًا مختصرًا عن هيكلية البرنامج ضمن المستودع.
اقتباسملاحظة: سنستخدم البرنامج نفسه وملف الأداء نفسه عند شرح الأداة Flame Chart لاحقًا في هذا الفصل.
تعرض لقطة الشاشة التالية خرج برنامج يوازن بين ثلاثة خوارزميات للفرز، وهي الفرز الفقاعي Bubble Sort والفرز الانتقائي Selection Sort والفرز السريع Quicksort؛ حيث يولِّد البرنامج لتنفيذ الاختبار بعض المصفوفات التي تضم أرقامًا صحيحةً عشوائيةً، ثم يفرزها باستعمال كل خوارزمية على حدة، وقد ركزنا في قسم الحالة العامة Recording overview على منطقة ظهرت فيها علامة طويلة لجافاسكربت:
تعرض الأداة Call Tree النتائج ضمن جدول يمثِّل فيه كل صف دالةً التَقطت منها الأداة عينةً واحدةً على الأقل ورتبت أسطرها حسب عدد العينات المأخوذة أثناء تواجدها في الدالة، ترتيبًا تنازليًا، بينما تمثِّل الأعمدة القيم التالية:
- العينات Samples: تشير إلى عدد العينات التي التُقطت عند تنفيذ دالة معينة بما في ذلك الدوال الأبناء وهي الدوال التي تُستدعيها هذه الدالة.
- الوقت الكلي Tota lTime: يقدَّر بالميلي ثانية ويمثِّل الوقت الكلي الذي تستغرقه شريحةً محددةً من التسجيل، كما يماثل تقريبًا عدد العينات.
- التكلفة الكلية Total Cost: يمثِّل النسبة المئوية للعدد الكلي للعينات في القسم المختار من التسجيل.
- التوقيت الذاتي Self Time: يمثِّل الوقت الذي يستغرقه تنفيذ دالة محددة دون النظر إلى زمن تنفيذ أبنائها، حيث يُقدَّر هذا الزمن من حالة المكدسات المُلتقَطة في اللحظات التي تكون فيها الدالة آخر عناصر المكدس leafmost function.
- التكلفة الذاتية Self Cost: تُحسب من التوقيت الذاتي على أساس نسبة مئوية للعدد الكلي من عينات القسم المحدد من التسجيل.
تمثِّل الأعمدة السابقة القيم الأهم في النسخة الحالية من الأداة Call Tree، ويُفضَّل إجراء تحسين للشيفرة انطلاقًا من الدوال التي تمتلك قيمة تكلفة ذاتية مرتفعة لأنها تستغرق وقتًا طويلًا في التنفيذ، أو لأنها تُستدعى بكثرة.
اقتباستوضيح: يُعَدّ استخدام الصيغة المعكوسة للأداة Call Tree مفيدًا للتركيز على هذا النوع من الدوال أو على هذه القيم للتكلفة الذاتية.
تخبرنا أخيرًا لقطة الشاشة السابقة عن شيء ربما نعرفه بالفعل، وهو أنّ خوارزمية الفرز الفقاعي فعالة جدًا، فعدد العينات الملتقَطة في القسم الذي يسجل عملها يزيد 6 مرات عن عدد عينات القسم الذي يسجل عمل خوارزمية الفرز الانتقائي، و13 مرة عن خوارزمية الفرز السريع.
التنقل ضمن الأداة Call Tree
يوجد سهم صغير إلى جوار اسم كل دالة، وسترى عند النقر على هذا السهم مسار استدعاء هذه الدالة رجوعًا إلى بدايته، أي من الدالة التي التقطت العينة أثناء تنفيذها وحتى الدالة الجذرية، إذ يمكن مثلًا توسيع الدالة ()bubbleSort
:
سيظهر المخطط البياني للاستدعاء كما يلي:
sortAll() -> sort() -> bubbleSort()
اقتباسملاحظة: على الرغم من أنّ التكلفة الذاتية للدالة
()sort
هي 1.45% وهي مختلفة عن قيمة الدالة()sort
الموجودة في سطر مستقل في القائمة، فذلك يدل على أنّ بعض العينات قد التُقِطت داخل الدالة()sort
نفسها بدلًا من الدوال التي تستدعيها، وقد تلاحظ وجود عدة مسارات تعود من نقطة معينة إلى المستوى الأعلى، ولنحاول توسيع الدالة()swap
:
التُقِطت 253 عينة داخل الدالة ()swap
، لكن يوجد مساران مختلفان يقودان إلى هذه الدالة، إذ تستخدِمها كلتا الدالتَين ()bubbleSort
و()selectionSort
، كما يمكننا ملاحظة أنّ 252 عينة من أصل 253 ضمن الدالة ()swap
قد التُقِطت من مسار الدالة ()bubbleSort
وعينةً واحدةً فقط من المسار الآخر. تشير هذه النتيجة إلى أنّ خوارزمية الفرز الفقاعي أقل فعاليةً مما نظن، فهي تتحمل في الواقع مسؤولية 252 عينة إضافية، أي حوالي 10% أخرى من التكلفة الكلية، كما يمكننا الحصول على المخطط البياني الكامل مع عدد العينات الموافق بمتابعة طريقة التحليل هذه:
sortAll() // 8 -> sort() // 37 -> bubbleSort() // 1345 -> swap() // 252 -> selectionSort() // 190 -> swap() // 1 -> quickSort() // 103 -> partition() // 12
بيانات منصة العمل
سترى بعض الأسطر التي تحمل قيمًا مثل Gecko أو Input & Events وغيرها، بحيث تمثِّل الاستدعاءات الداخلية للمتصفح، كما تقدِّم لك بيانات هذه الأسطر معلومات قيمةً أيضًا، فقد لا تجد أية عينات تشير إلى مدى صعوبة تنفيذ شيفرة موقعك في المتصفح، وستجد في مثالنا 679 عينة مرتبطة بالقيمة Gecko، وهي ثاني أكبر مجموعة من العينات بعد عينات الدالة ()bubbleSort
، فلنوسِّع إذًا معلومات Gecko:
تشيرالنتائج إلى أنّ مصدر 614 عينة من هذه العينات أو حوالي 20% من التكلفة الكلية هي من استدعاء الدالة ()sort
، فإذا نظرنا إلى شيفرة هذه الدالة، فسنلاحظ بوضوح أنّ سبب التكلفة المرتفعة لعمل المنصة -أو مجهود المتصفح- هو الاستدعاء المتكرر للتابع ()console.log
:
function sort(unsorted) { console.log(bubbleSort(unsorted)); console.log(selectionSort(unsorted)); console.log(quickSort(unsorted)); }
إذًا من الأفضل التفكير بطريقة أخرى لتنفيذ الأمر. لاحظ أيضًا أنّ زمن الخمول idle time يصنَّف على أساس Gecko، أي أنّ أقسام ملف الأداء التي لم تُنفَّذ أثناء تسجيلها أيّ شيفرة جافاسكربت أثناء التنفيذ ستصنف على أنها عينات Gecko، ولا يتعلق هذا التصنيف طبعًا بأداء الصفحة.
اقتباسملاحظة: لا تفصل الأداة Call Tree بيانات منصة العمل ضمن دوال مستقلة لما يسببه ذلك من تشويش لمستخدِم هذه البيانات وخاصةً لمن لا يستخدِم متصفح فايرفوكس، فإذا أردت معرفة تفاصيل أكثر، فعليك بتفعيل الخيار Show Gecko Platform Data ضمن الإعدادات.
الصيغة المعكوسة (المقلوبة) للأداة Call Tree
ببساطة هي صيغة ينعكس فيها ترتيب جميع المكدّسات، بحيث تكون آخر الدوال المستدعاة في قمة المكدس، وبهذه الطريقة سيركِّز عرض البيانات أكثر على معلومات التوقيت الذاتي للدوال Self Time، وبالتالي ستحصل على معلومات أكثر حساسية في شيفرتك، ولإظهار الأداة بالصيغة المعكوسة، انقر على أيقونة التبديل في أقصى يمين نافذة الأداء ثم اختر Invert Call Tree.
الأداة Flame Char
تعرض لك هذه الأداة حالة مكدس جافاسكربت كل ميلي ثانية أثناء تسجيل ملف الأداء، كما يتيح لك ذلك معرفة الدالة التي يجري تنفيذها في كل لحظة من لحظات التسجيل وزمن تنفيذها ومسار استدعائها، كما تُستخدَم الأداتين Call Tree وFlame Chart في تحليل شيفرة جافاسكربت في موقعك وتستخدمان البيانات ذاتها المتمثلة بعينات من المكدس الخاص بمحرك جافاسكربت الملتقَطة دوريًا أثناء تسجيل ملف الأداء.
ستعرض لك الأداة Flame Chart متى يجري تنفيذ دالة معينة خلال زمن التسجيل بينما تنظم الأداة Call Tree البيانات لعرض الدوال التي يقضي البرنامج وقتًا أكبر في تنفيذها، كما تعرض لك بصورة أساسية حالة مكدس الاستدعاء في أية لحظة من لحظات زمن التسجيل، وإليك لقطة شاشة تُظهر بيانات الأداة Flame Chart لشريحة من ملف الأداء:
يمكننا في البداية ملاحظة أننا اخترنا شريحةً صغيرةً من التسجيل لعرض بيانات Flame Chart لها ضمن لوحة النظرة العامة، لأن البيانات التي تقدمها هذه الأداة كثيرة وتصعب متابعتها، لذلك كان لا بد من تضييق نطاق العرض، كما يمثِّل المحور X في لقطة الشاشة السابقة الزمن، في حين تغطي هذه اللقطة الفترة الزمنية الممتدة بين اللحظة 1435 ميلي ثانية و1465 ميلي ثانية، وتتوزع أيضًا على امتداد المحور Y، الدوال الموجودة في مكدس الاستدعاء خلال هذه الفترة من الدوال الأعلى مستوى في الأعلى إلى أخفضها مستوًى في الأسفل، وصُنِّفت الدوال وفق نظام لوني ليسهُل التمييز بينها.
يمنحك هذا الأسلوب في عرض البيانات إمكانية معرفة الدالة التي يجري تنفيذها في أية لحظة خلال فترة التسجيل، والوقت المستغرَق في تنفيذها ومسار استدعائها.
تغيير مجال العرض وإزاحة الشرائح
ينبغي أن تكون قادرًا على التنقل بين البيانات المعروضة بسهولة لكي تستطيع العمل بفعالية أكبر، حيث تقدِّم الأداة Flame Chart وسيلتَي تحكم لهذا الغرض:
- Zoom (تغيير مجال العرض): لزيادة أو إنقاص مجال الشريحة المختارة من ملف التسجيل الكامل الذي يُعرَض ضمن الأداة Flame Chart، حيث تستطيع استخدام هذه الوسيلة كما يلي:
- دحرجة دولاب الفأرة للأعلى والأسفل ضمن Flame Chart.
- تحريك إصبعَين معًا للأعلى أو للأسفل على لوحة اللمس ضمن Flame Chart.
- Pan (التنقل بين الشرائح): إزاحة الشريحة التي اخترتها من ملف التسجيل الكامل يمينًا أو يسارًا، إذ تستطيع استخدام هذه الوسيلة كما يلي:
- انقر على الشريحة المختارة ضمن لوحة النظرة العامة واسحبها بالاتجاه المطلوب.
- انقر واسحب في أي مكان ضمن Flame Chart.
مثال تطبيقي
سنلقي نظرةً على مثال بسيط هو نفسه البرنامج الذي استخدمناه سابقًا لتتابع كيف ستكشف الأداة Flame Chart سلوك برنامجك ولتوضيح عمل الأداة Call Tree، كما سنستخدم ملف الأداء نفسه، فقد وجدنا أنُ تسلسل الاستدعاءات وتعداد العينات المقابل لكل منها في ملف الأداء هو كما يلي:
sortAll() // 8 -> sort() // 37 -> bubbleSort() // 1345 -> swap() // 252 -> selectionSort() // 190 -> swap() // 1 -> quickSort() // 103 -> partition() // 12
اخترنا في البداية المقطع الذي كان البرنامج فيه مشغولًا بأكمله:
لاحظ وجود الدالة ()sortAll
الملونة بالأرجواني في القمة، حيث يمتد زمن تنفيذها من بداية البرنامج حتى نهايته، وتأتي تحتها مباشرةً الدالة ()sort
باللون الأخضر الزيتوني، ويليها تعاقب استدعاءات مثل أسنان المشط لكل خوارزمية من خوارزميات الفرز الثلاث، ولنقرِّب المشهد أكثر:
تمتد الشريحة مدة 140 ميلي ثانية وتعرض تفاصيلًا أكثر عن الدوال التي تستدعيها الدالة ()sort
. لاحظ الشيفرة التي تكوّن الدالة ()sort
:
function sort(unsorted) { console.log(bubbleSort(unsorted)); console.log(selectionSort(unsorted)); console.log(quickSort(unsorted)); }
تمثِّل العلامة التي يبدو عنوانها "…bubb" وملوّنةً بالأخضر الزيتوني الدالة ()bubbleSort
، في حين تمثل العلامة الأخرى التي تبدو باللون الأخضر الصرف بقية دوال الفرز. لاحظ أنّ كتلة دالة الفرز الفقاعي ()bubbleSort
ذات عرض أكبر -أي تمتد لفترة أطول- من البقية، كما يمكن ملاحظة مجموعة دوال تستدعيها الدالة ()bubbleSort
وتبدو باللون الأرجواني، ولنقرّب المشهد مرة أخرى:
يساوي عرض الشريحة الأخيرة التي اخترناها حوالي 20 ميلي ثانية، وسنرى بوضوح أنّ العلامة الأرجوانية أسفل الدالة ()bubbleSort
هي استدعاءات للدالة ()swap
، فإذا أحصينا هذه الاستدعاءات، فسترى أنها 253 وفقًا للأداة Call Tree، كما تشير Call Tree إلى وقوع كل الاستدعاءات السابقة تحت الدالة ()bubbleSort
ماعدا استدعاء وحيد يقع تحت الدالة ()selectionSort
، كما يمكن أيضًا رؤية وجود علامتين خضراوين للدالتين ()selectionSort
و()quickSort
، وسنرى استدعاءات لمنصة العمل في الفترة ما بين استدعاءات دوال الفرز، كما من المرجح أن يكون سببها استدعاء الدالة ()console.log
من داخل الدالة ()sort
.
الأداة Allocations
تعرض لك الأداة Allocations الدوال التي تحجز مساحةً أكبر من الذاكرة خلال فترة تسجيل ملف الأداء، ويُعَدّ الأمر مهمًا من ناحية الأداء، لأن حجز مساحات كبيرة من الذاكرة أو إشغال مناطق كثيرة منها قد يؤدي إلى وقوع حدث تجميع الموارد المستهلكة garbage collection، والذي يؤثر بدوره سلبًا على استجابة الصفحة، كما تُعَدّ هذه الأداة جديدةً في فايرفوكس 46.
عليك تفعيل خيار تسجيل حجوزات الذاكرة Record Allocations ضمن إعدادات الأداة Performance قبل تسجيل ملف الأداء لكي تشاهد هذه الأداة، وعند تسجيل الملف بعد ذلك سترى نافذةً فرعيةً جديدةً عنوانها Allocations في شريط الأدوات.
تشريح لوحة الأداة Allocations
تبدو اللوحة كما في الصورة التالية:
تأخذ الأداة عينات دوريًا من مساحات الذاكرة المحجوزة K بحيث يمثِّل كل صف دالةً أُخذِت عينة واحدة على الأقل من مساحة الذاكرة التي تحجزها أثناء تسجيل ملف الأداء، كما يضم جدول عرض البيانات الأعمدة التالية:
- Self Count العداد الذاتي: عدد عينات الحجز التي التُقِطت ضمن الدالة المحددة، وتُعرَض أيضًا على أساس نسبة مئوية من عدد العينات الكلي.
- Self Byte عدد البايتات الذاتي: العدد الكلي للبايتات الموجودة في عينة ملتقَطة داخل دالة، تُعرَض أيضًا على أساس نسبة مئوية من الكمية الكلية، كما تُرتَّب الأعمدة وفقًا لعدد البايتات الذاتي.
سنجد في مثالنا السابق ما يلي:
-
تمثل العينات الملتقَطة في الدالة
()signalLater
ما مقداره 28.57% من العدد الكلي للعينات البالغ 8904. - تحجز هذه العينات 1102888 بايت، أي ما مقداره 30.01% من الذاكرة الكلية المحجوزة في جميع العينات.
سترى الأماكن التي تُستدعى منها كل دالة بالنقر على السهم الصغير بجوار اسمها:
لاحظ كيف استُدعيت الدالة ()signalLater
من مكانين هما ()removeInner
و()setSelectionInner
،إذ ستتمكن الآن من الرجوع خطوةً ضمن المكدس لفهم سياق حجز الذاكرة بصورة أفضل.
التكلفة الذاتية والتكلفة الكلية
تعرض لك الأداة مجموعتين من الأعمدة يبدأ عنوان الأولى بالكلمة Self والثانية بالكلمة Total، إذ تسجِّل الأولى العينات الملتقَطة من دالة واحدة، بينما تسجِّل الثانية العينات الملتقَطة من هذه الدالة أو الدوال التي استدعتها هذه الدالة، كما ستتطابق هاتين المجموعتين بالنسبة للدوال التي تمثل قمة المكدس طالما أن الدوال الأخيرة في المكدس ستظهر في الأعلى، أي ما نراه هو صيغة معكوسة لمكدس الاستدعاء، لكن إذا بدأت التراجع إلى الخلف في المكدس، فسترى الفرق بين المجموعتين:
التقَطت الأداة 8904 عينةً في الدالة ()signalLater
، لكن هذه الدالة قد استُدعيت من مكانين ()removeInner
و()setSelectionInner
، وتبدو قيمة العداد الذاتي لهما 0 بمعنى أنهما لا تحجزان مباشرةً أية ذاكرة، لكن قيمة العداد الكلي للدالة ()removeInner
هي 8901 وقيمته للدالة ()setSelectionInner
هي 3 فقط، أي أنّ معظم الحجوزات التي نراها في الدالة ()signalLater
مصدرها الدالة ()removeInner
.
حجز الذاكرة وتجميع الموارد المستهلكة
إنّ أية معلومات عن الذاكرة التي يحجزها موقع جديرة بالاهتمام، لكن الرابط الحقيقي بين استجابة موقع وكمية الذاكرة المحجوزة هو موضوع تجميع الموارد المستهلكة GC، كما يتحقق محرك التنفيذ من وجود كائنات مستهلكة لا يمكن الوصول إليها في كومة الذاكرة الخاص بالبرنامج في جميع اللغات التي تعتمد أسلوب تجميع الموارد المستهلكة مثل جافاسكربت، ثم يحرر الذاكرة التي تحجزها تلك الكائنات، وعند تنفيذ هذه العملية يتوقف محرك جافاسكربت مؤقتًا ويُعلَّق عمل البرنامج وتتوقف استجابته.
يُنفِّذ SpiderMonkey محرك جافاسكربت على فايرفوكس هذه العملية تصاعديًا بخطوات صغيرة لتخفيف هذا الأثر على الاستجابة، فيسمح باستمرارية عمل البرنامج ما بين هذه الخطوات، لكن لا بد في بعض الحالات من تنفيذ عملية تجميع موارد مستهلكة لا تصاعدي، وبالتالي على البرنامج الانتظار حتى نهاية العملية، كما تظهر أحداث تجميع الموارد المستهلكة باللون الأحمر في لوحة الأداة Waterfall، وقد تلاحظ رايات حمراء طويلة ترتبط بانعدام استجابة البرنامج قد تمتد فترة عدة مئات من الميلي ثانية.
ما الذي تستطيع فعله عندما ترى أحداث GC في ملف أداء برنامجك؟ يستخدِم SpiderMonkey مجموعةً معقدةً من الطرق الاستدلالية لتقدير الحاجة إلى استخدام طريقة معينة في تجميع الموارد المستهلكة، لكن الضغوطات الناتجة عن حجز الذاكرة -أي حجز مساحات كبيرة أو معدل حجز مرتفع للذاكرة- ستدفع SpiderMonkey إلى تنفيذ عمليات GC، وعلى الأغلب التجميع الكامل اللاتصاعدي عوضًا عن التصاعدي.
إذا وقعت أحداث تجميع الموارد المستهلكة بسبب ضغوطات ناتجة عن حجز الذاكرة، فسيظهر في هذه الحالة رابطًا بعنوان عرض المسببات الناتجة عن حجز الذاكرة Show Allocation Triggers ضمن الشريط الجانبي إلى يمين نافذة الأداة Waterfall، فإذا نقرت على هذا الرابط، فستنتقل أدوات التطوير إلى عرض النافذة Allocations وستختار الفترة الزمنية الممتدة من نهاية آخر دورة لتجميع الموارد المستهلكة حتى بداية الدورة التي نقرت عليها، وسيعرض لك ذلك كل حجوزات الذاكرة التي أدت بمجموعها إلى وقوع حدث تجميع الموارد المستهلكة، فإذا واجهتك هذه المشكلة، فحاول إذا استطعت اختزال عدد أو حجم حجوزات الذاكرة:
- هل يمكنك حجز ذاكرة محدودة؟ أي فقط عندما تحتاجها بدلًا من حجزها منذ البداية.
- هل تجري عملية الحجز ضمن حلقة؟ إذا كان الأمر كذلك، فهل بالإمكان إعادة استخدام الذاكرة المحجوزة نفسها عند كل تكرار للحلقة؟
ترجمة -وبتصرف- للمقالات التالية:
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.