لوحة أداء أسرع بنسبة 400% من خلال الأداء

Andrés Olivares
Andrés Olivares
Nancy Li
Nancy Li

بغض النظر عن نوع التطبيق الذي تطوّره، يُعدّ تحسين أدائه وضمان أنّه يتم تحميله سريعًا وتقديم تفاعلات سلسة أمرًا بالغ الأهمية لتجربة المستخدم ونجاح التطبيق. تتمثل إحدى طرق القيام بذلك في فحص نشاط أحد التطبيقات باستخدام أدوات التحليل لمعرفة ما يحدث خفية أثناء تشغيله خلال فترة زمنية. لوحة الأداء في "أدوات مطوري البرامج" هي أداة رائعة لتحليل أداء تطبيقات الويب وتحسينها. إذا كان تطبيقك قيد التشغيل في Chrome، سيمنحك هذا نظرة عامة مرئية تفصيلية حول الإجراءات التي ينفّذها المتصفّح أثناء تنفيذ التطبيق. يمكن أن يساعدك فهم هذا النشاط في تحديد الأنماط والعوامل السلبية ونقاط الأداء التي يمكنك العمل عليها لتحسين الأداء.

يوضِّح لك المثال التالي كيفية استخدام لوحة الأداء.

إعداد سيناريو التحليل وإعادة صياغته

حدّدنا مؤخرًا هدفًا لتحسين أداء لوحة الأداء. وعلى وجه الخصوص، أردنا أن تحمل هذه الأداة كميات كبيرة من بيانات الأداء بسرعة أكبر. هذا هو الحال، على سبيل المثال، عند تحليل مواصفات عمليات طويلة الأمد أو معقدة أو تسجيل بيانات عالية الدقة. لتحقيق ذلك، احتجنا في المقام الأول إلى فهم طريقة أداء التطبيق وسبب أدائه، وقد تم تحقيق ذلك باستخدام أداة إنشاء ملفات تعريفية.

كما تعلم، إنّ "أدوات مطوري البرامج" في حد ذاته هو تطبيق ويب. وبناءً على ذلك، يمكن تحليله باستخدام لوحة الأداء. لتحديد هذه اللوحة نفسها، يمكنك فتح "أدوات مطوري البرامج"، ثم فتح مثيل آخر من "أدوات مطوري البرامج" المرفق بها. تُعرف عملية الإعداد هذه في Google باسم أدوات مطوّري البرامج في "أدوات مطوري البرامج".

عندما يكون الإعداد جاهزًا، يجب إعادة إنشاء السيناريو المراد تحليله وتسجيله. لتجنُّب حدوث أي التباس، ستتم الإشارة إلى نافذة "أدوات مطوري البرامج" الأصلية على أنّها "المثيل من "أدوات مطوري البرامج" في حين يُشار إلى النافذة التي تفحص المثيل الأول باسم "مثيل أدوات مطوري البرامج الثاني".

لقطة شاشة لمثيل في "أدوات مطوري البرامج" يفحص العناصر في "أدوات مطوري البرامج" نفسه
"أدوات مطوري البرامج": فحص "أدوات مطوري البرامج" باستخدام "أدوات مطوري البرامج".

في مثيل "أدوات مطوري البرامج" الثاني، ترصد لوحة الأداء (التي ستُسمّى لوحة الأداء من الآن فصاعدًا) النسخة الأولى من أدوات مطوّري البرامج لإعادة إنشاء السيناريو، وهو ما يؤدّي إلى تحميل ملف شخصي.

في مثيل "أدوات مطوري البرامج" الثاني، يبدأ تسجيل مباشر، وفي المرة الأولى، يتم تحميل ملف شخصي من ملف على القرص. يتم تحميل ملف كبير للتعريف بدقة على أداء معالجة المدخلات الكبيرة. عند انتهاء التحميل في كلتا الحالتين، تظهر بيانات تحليل الأداء، التي تُعرف عادةً باسم trace، في مثيل "أدوات مطوري البرامج" الثاني على لوحة الأداء التي تُحمِّل ملفًا شخصيًا.

الحالة الأولية: تحديد فرص التحسين

بعد انتهاء التحميل، تمت ملاحظة ما يلي في مثيل لوحة الأداء الثاني في لقطة الشاشة التالية. يجب التركيز على نشاط سلسلة التعليمات الرئيسية التي تظهر ضمن المسار المسمّى الرئيسية. يتضح أنه هناك خمس مجموعات كبيرة من الأنشطة في مخطط اللهب. وهي تشمل المهام التي يستغرق فيها التحميل معظم الوقت. كان الوقت الإجمالي لهذه المهام حوالي 10 ثوانٍ. في لقطة الشاشة التالية، تُستخدم لوحة الأداء للتركيز على كل مجموعة من مجموعات الأنشطة هذه لمعرفة ما يمكن العثور عليه.

لقطة شاشة للوحة الأداء في "أدوات مطوري البرامج" تفحص تحميل تتبُّع الأداء في لوحة الأداء لمثيل آخر من أدوات مطوّري البرامج يستغرق تحميل الملف الشخصي 10 ثوانٍ تقريبًا. يتم تقسيم هذا الوقت في الغالب إلى خمس مجموعات رئيسية من الأنشطة.

مجموعة النشاط الأولى: عمل غير ضروري

واتضح أن المجموعة الأولى من النشاط كانت عبارة عن رمز قديم لا يزال قيد التشغيل، ولكنه لم يكن ضروريًا حقًا. في الأساس، كل شيء تحت الكتلة الخضراء المسماة processThreadEvents هدر جهدًا. كان ذلك فوزًا سريعًا. أدت إزالة استدعاء الدالة هذا إلى توفير 1.5 ثانية من الوقت تقريبًا. رائع!

مجموعة النشاط الثانية

في مجموعة النشاط الثانية، لم يكن الحل بسيطًا مثل الأول. استغرقت المهمة buildProfileCalls حوالي 0.5 ثانية، ولم يكن من الممكن تجنُّب هذه المهمة.

لقطة شاشة للوحة الأداء في "أدوات مطوري البرامج" تفحص مثيل لوحة أداء آخر تستغرق المهمة المرتبطة بدالة BuildProfileCalls حوالي 0.5 ثانية.

من باب الفضول، تم تفعيل خيار الذاكرة في لوحة الأداء لإجراء المزيد من التحقيق، وتبيّن لنا أنّ نشاط buildProfileCalls كان يستهلك أيضًا مساحة كبيرة من الذاكرة. يمكنك هنا الاطّلاع على القفزة المفاجئة في الرسم البياني الخطي الأزرق خلال وقت تشغيل buildProfileCalls، ما يشير إلى حدوث تسرّب محتمل في الذاكرة.

لقطة شاشة لمحلّل الذاكرة في "أدوات مطوري البرامج" يقيّم استهلاك الذاكرة للوحة الأداء يقترح الفاحص أن دالة BuildProfileCalls مسؤولة عن تسرب الذاكرة.

وللمتابعة في هذا الأمر، استخدمنا لوحة الذاكرة (لوحة أخرى في "أدوات مطوري البرامج"، مختلفة عن درج الذاكرة في لوحة الأداء) للتحقيق في المشكلة. ضمن لوحة Memory (الذاكرة)، تم اختيار نوع التحليل "تخصيص عيّنات"، الذي سجّل لقطة للوحة الأداء التي تُحمّل الملف التعريفي لوحدة المعالجة المركزية (CPU).

لقطة شاشة للحالة الأولية لمحلّل الذاكرة يتم تمييز خيار "تخصيص العيّنات" بمربّع باللون الأحمر، ويشير إلى أنّ هذا الخيار هو الأفضل لتحليل ذاكرة JavaScript.

تعرض لقطة الشاشة التالية اللقطة المجمّعة التي تم جمعها.

لقطة شاشة للمحلِّل للذاكرة، مع اختيار عملية تستند إلى "ضبط" تستهلك قدرًا كبيرًا من الذاكرة.

من خلال لقطة كومة الذاكرة هذه، لوحظ أنّ فئة Set تستهلك الكثير من الذاكرة. عند التحقّق من نقاط الاستدعاء، تبيّن لنا أنّنا كنا نخصّص خصائص من النوع Set للعناصر التي تم إنشاؤها بأحجام كبيرة بدون داعٍ. ازدادت هذه التكلفة واستهلك الكثير من الذاكرة، إلى الحد الذي كان من الشائع أن يتعطل التطبيق فيه عند إدخال بيانات كبيرة.

تُعد المجموعات مفيدة لتخزين العناصر الفريدة وتوفير عمليات تفرد محتواها، مثل إزالة تكرار مجموعات البيانات وتوفير عمليات بحث أكثر كفاءة. ومع ذلك، لم تكن هذه الميزات ضرورية لأنه تم ضمان أن تكون البيانات المخزنة فريدة من المصدر. وعلى هذا النحو، لم تكن المجموعات ضرورية في المقام الأول. لتحسين تخصيص الذاكرة، تم تغيير نوع السمة من Set إلى مصفوفة عادية. بعد تطبيق هذا التغيير، تم أخذ لقطة أخرى لكوم الذاكرة الذي تمت ملاحظة انخفاضه في تخصيص الذاكرة. على الرغم من عدم تحقيق تحسينات كبيرة في السرعة مع هذا التغيير، كانت الفائدة الثانوية هي تعطل التطبيق بشكل أقل تكرارًا.

لقطة شاشة للمحلّل تم تغيير العملية المستندة إلى المجموعة التي كانت تستهلك الذاكرة سابقًا لتستخدم صفيفًا عاديًا، ما أدى إلى انخفاض تكلفة الذاكرة بشكل كبير.

مجموعة النشاط الثالثة: موازنة المقايضات في هيكل البيانات

القسم الثالث فريد من نوعه: كما ترى في المخطط اللهب أنه يتكون من أعمدة ضيقة لكنها طويلة، مما يشير إلى استدعاءات دالة عميقة، والتكرارات العميقة في هذه الحالة. واستمر هذا القسم حوالى 1.4 ثانية. بالنظر إلى أسفل هذا القسم، تبيّن لنا أنّه تم تحديد عرض هذه الأعمدة حسب مدة إحدى الدوالّ: appendEventAtLevel، ما يشير إلى أنّه قد يكون معقودًا.

داخل تنفيذ دالة appendEventAtLevel، برز شيء واحد. بالنسبة إلى كل إدخال بيانات في الإدخال (وهو ما يُعرَف في الرمز باسم "الحدث")، تمت إضافة عنصر إلى الخريطة التي تتبَّعت الموضع الرأسي لإدخالات المخطط الزمني. كان هذا مشكلة، لأن كمية العناصر المخزنة كانت كبيرة جدًا. تتميز "خرائط Google" بالسرعة في إجراء عمليات البحث المستندة إلى المفاتيح، ولكن هذه الميزة لا تتوفّر مجانًا. كلما كبر حجم الخريطة، يمكن أن تصبح إضافة البيانات إليها، على سبيل المثال، مكلفة بسبب إعادة التجزئة. تصبح هذه التكلفة ملحوظة عند إضافة كميات كبيرة من العناصر إلى الخريطة بشكل متتابع.

/**
 * Adds an event to the flame chart data at a defined vertical level.
 */
function appendEventAtLevel (event, level) {
  // ...

  const index = data.length;
  data.push(event);
  this.indexForEventMap.set(event, index);

  // ...
}

لقد جربنا نهجًا آخر لم يطلب منا إضافة عنصر في الخريطة لكل إدخال في المخطط اللهب. كان التحسين ملحوظًا، مما أكد أن المؤثِّر السلبي كان مرتبطًا بالفعل بالنفقات العامة التي تكبدتها إضافة جميع البيانات إلى الخريطة. انخفض الوقت الذي استغرقته مجموعة النشاط من 1.4 ثانية تقريبًا إلى حوالى 200 ملّي ثانية.

قبل:

لقطة شاشة للوحة الأداء قبل إجراء تحسينات على الدالة appendEventAtLevel كان الوقت الإجمالي لتشغيل الدالة هو 1372.51 مللي ثانية.

بعد:

لقطة شاشة للوحة الأداء بعد إجراء تحسينات على الدالة appendEventAtLevel كان الوقت الإجمالي لتشغيل الدالة 207.2 ملي ثانية.

مجموعة النشاط الرابعة: تأجيل العمل غير المهم وذاكرة التخزين المؤقت للبيانات لمنع تكرار العمل

عند تكبير هذه النافذة، يتضح لنا أن هناك كتلتين متطابقتين تقريبًا من استدعاءات الدوال. بالنظر إلى اسم الدوال المسماة، يمكنك استنتاج أنّ هذه الوحدات تتألّف من رموز برمجية تتألف من أشجار (على سبيل المثال، بأسماء مثل refreshTree أو buildChildren). في الواقع، الرمز ذو الصلة هو الرمز الذي يُنشئ طرق العرض الشجري في الدرج السفلي من اللوحة. ومن المثير للاهتمام أنّ هذه العروض الشجرية لا تظهر بعد التحميل مباشرةً. بدلاً من ذلك، على المستخدم اختيار العرض التدرّجي للحساب (علامات التبويب "من أسفل إلى أعلى" و"شجرة المكالمات" و "سجل الأحداث" في الدرج) لكي يتم إظهار الأشجار. بالإضافة إلى ذلك، وكما هو موضّح من خلال لقطة الشاشة، تمّ تنفيذ عملية بناء الشجرة مرتين.

لقطة شاشة للوحة الأداء تعرض العديد من المهام المتكرّرة التي يتم تنفيذها حتى إذا لم تكن ضرورية يمكن تأجيل هذه المهام لتنفيذها عند الطلب، بدلاً من تنفيذها في وقت مبكر.

هناك مشكلتان حدّدناهما في هذه الصورة:

  1. كانت مهمة غير أساسية تعيق أداء وقت التحميل. ولا يحتاج المستخدمون دائمًا إلى مخرجاته. ولذلك، فإن المهمة ليست بالغة الأهمية لتحميل الملف الشخصي.
  2. لم يتم تخزين نتيجة هذه المهام في ذاكرة التخزين المؤقت. لهذا السبب تم حساب الأشجار مرتين، على الرغم من عدم تغيير البيانات.

بدأنا بتأجيل حساب العرض التدرّجي للحساب عندما فتح المستخدم العرض التدرّجي للحساب يدويًا. عندها فقط، هل يستحق الأمر دفع سعر صنع هذه الأشجار. كان الوقت الإجمالي لتشغيل هذه المرتَين 3.4 ثانية تقريبًا، لذا كان تأجيله قد أحدث فارقًا كبيرًا في مدّة التحميل. وما زلنا نبحث في التخزين المؤقت لهذه الأنواع من المهام أيضًا.

مجموعة الأنشطة الخامسة: تجنَّب التدرجات الهرمية المعقّدة للمكالمات متى أمكن

بالنظر إلى هذه المجموعة عن كثب، كان واضحًا أنّه تم استدعاء سلسلة اتصال معيّنة بشكل متكرّر. ظهر نفس النمط 6 مرات في أماكن مختلفة في مخطط اللهب، وكانت المدة الإجمالية لهذه النافذة حوالي 2.4 ثانية!

لقطة شاشة للوحة الأداء تعرض ست استدعاءات منفصلة للدوال لإنشاء الخريطة المصغّرة نفسها للتتبُّع، ولكل منها حزم استدعاءات عميقة.

التعليمة البرمجية ذات الصلة التي يتم استدعائها عدة مرات هي الجزء الذي يعالج البيانات التي سيتم عرضها على "الخريطة المصغرة" (نظرة عامة على نشاط المخطط الزمني في أعلى اللوحة). لم يكن من الواضح سبب حدوث ذلك عدة مرات، لكن بالتأكيد لم يكن يجب أن يحدث 6 مرات! في الواقع، يجب أن يظل ناتج التعليمة البرمجية حديثًا إذا لم يتم تحميل ملف شخصي آخر. من الناحية النظرية، يجب تشغيل التعليمة البرمجية مرة واحدة فقط.

أثناء التحقيق، تم اكتشاف أنه تم استدعاء التعليمة البرمجية ذات الصلة نتيجة لأجزاء متعددة في مسار التحميل من خلال استدعاء الدالة التي تحسب الخريطة المصغّرة بشكل مباشر أو غير مباشر. وذلك لأن مدى تعقيد الرسم البياني للمكالمات الخاص بالبرنامج تطور بمرور الوقت، وتمت إضافة المزيد من التبعيات إلى هذا الرمز البرمجي بدون قصد. لا يتوفّر حل سريع لهذه المشكلة. تعتمد طريقة حلها على بنية قاعدة التعليمات البرمجية المعنية. في حالتنا، كان علينا تقليل تعقيد التسلسل الهرمي للمكالمات قليلاً وإضافة فحص لمنع تنفيذ الرمز إذا ظلّت بيانات الإدخال بدون تغيير. بعد تنفيذ ذلك، حصلنا على هذه النظرة للجدول الزمني:

لقطة شاشة للوحة الأداء تعرض طلبات الدوال الست المنفصلة لإنشاء الخريطة المصغّرة نفسها للتتبُّع إلى مرّتَين فقط

لاحظ أن تنفيذ عرض الخريطة المصغرة تحدث مرتين، وليس مرة واحدة. وذلك لأن هناك خريطتين صغيرتين يتم رسمهما لكل ملف شخصي: أحدهما للنظرة العامة أعلى اللوحة، والآخر للقائمة المنسدلة التي تحدد الملف الشخصي المرئي حاليًا من السجل (يحتوي كل عنصر في هذه القائمة على نظرة عامة على الملف الشخصي الذي يختاره). على الرغم من ذلك، تتضمّن هذه الفيديوهات المحتوى نفسه، لذا يجب أن تتم إعادة استخدام أحدهما مع الآخر.

بما أنّ هذه الصور المصغّرة هي صور مرسومة على لوحة رسم، كانت المسألة متعلّقة باستخدام drawImage أداة لوحة الرسم، وبالتالي تم تشغيل الرمز مرة واحدة فقط لتوفير المزيد من الوقت. ونتيجة لهذا الجهد، تم تقليل مدة المجموعة من 2.4 ثانية إلى 140 مللي ثانية.

الخلاصة

بعد تطبيق جميع هذه الإصلاحات (بالإضافة إلى بعض عمليات الإصلاح الأخرى الصغيرة هنا وهناك)، بدا تغيير المخطط الزمني لتحميل الملف الشخصي كما يلي:

قبل:

لقطة شاشة للوحة الأداء تعرض تحميل تتبُّع الإحالات الناجحة قبل إجراء التحسينات. استغرقت العملية حوالي عشر ثوانٍ.

بعد:

لقطة شاشة للوحة الأداء تعرض تحميل بيانات التتبُّع بعد إجراء التحسينات تستغرق هذه العملية الآن ثانيتَين تقريبًا.

كان وقت التحميل بعد إجراء التحسينات ثانيتَين، ما يعني أنّه تم تحسين بنسبة%80 تقريبًا بجهد منخفض نسبيًا، لأنّ معظم الإجراءات التي تم تنفيذها كانت تتضمّن إصلاحات سريعة. بطبيعة الحال، كان من الضروري تحديد ما يجب فعله في البداية بشكل صحيح، وكانت لوحة الأداء هي الأداة المناسبة لذلك.

من المهم أيضًا إبراز أن هذه الأرقام خاصة بالملف الشخصي الذي يتم استخدامه كموضوع للدراسة. وقد كان الملف الشخصي مثيرًا للاهتمام بالنسبة إلينا لأنه كان كبيرًا للغاية. ومع ذلك، وبما أنّ مسار المعالجة هو نفسه لكل ملف شخصي، ينطبق التحسين الكبير الذي تم تحقيقه على كل ملف شخصي يتم تحميله في لوحة الأداء.

الخلاصات

هناك بعض الدروس التي يمكن استخلاصها من هذه النتائج في ما يتعلق بتحسين أداء تطبيقك:

1- الاستفادة من أدوات التحليل لتحديد أنماط الأداء في وقت التشغيل

تعتبر أدوات التحليل مفيدة للغاية لفهم ما يحدث في تطبيقك أثناء تشغيله، خاصة لتحديد الفرص المتاحة لتحسين الأداء. تُعد لوحة الأداء في Chrome DevTools خيارًا رائعًا لتطبيقات الويب، لأنّها الأداة الأساسية لتحليل بيانات الويب في المتصفّح، ويتم الحفاظ عليها باستمرار كي يتم تحديثها بأحدث ميزات النظام الأساسي للويب. أصبح الأمر الآن أسرع بكثير أيضًا! 😉

استخدم العينات التي يمكن استخدامها كأعباء عمل تمثيلية واكتشف ما يمكنك العثور عليه!

2- تجنُّب هيكليّات المكالمات المعقّدة

قدر الإمكان، تجنب جعل الرسم البياني للمكالمات معقدًا للغاية. مع التسلسلات الهرمية للاستدعاءات المعقدة، من السهل إدخال تراجعات في الأداء ويصعب فهم سبب تشغيل التعليمات البرمجية كما هي، مما يجعل من الصعب إجراء التحسينات.

3- تحديد العمل غير الضروري

من الشائع أن تحتوي قواعد الترميز القديمة على تعليمات لم تعُد مطلوبة. وفي حالتنا، كانت الرموز القديمة وغير الضرورية تأخذ جزءًا كبيرًا من إجمالي وقت التحميل. وكانت إزالة هذه الفاكهة هي الفاكهة الأكثر تشويقًا.

4. استخدم هياكل البيانات بشكل مناسب

استخدم هياكل البيانات لتحسين الأداء، ولكن أيضًا تعرّف على التكاليف والمقايضات التي يجلبها كل نوع من أنواع هيكل البيانات عند تحديد تلك التي تريد استخدامها. لا يقتصر هذا الأمر على تعقيد المساحة لبنية البيانات نفسها فقط، بل أيضًا التعقيد الزمني للعمليات السارية.

5- تخزين النتائج في ذاكرة التخزين المؤقت لتجنب العمل المكرر للعمليات المعقدة أو المتكررة

إذا كان تنفيذ العملية مكلفة، فمن المنطقي تخزين نتائجها في المرة القادمة التي تكون مطلوبة. كما أنه من المنطقي القيام بذلك إذا تم إجراء العملية عدة مرات - حتى لو لم تكن كل مرة على حدة مكلفة للغاية.

6. تأجيل العمل غير الحرج

إذا لم تكن هناك حاجة إلى ناتج مهمة على الفور وكانت عملية تنفيذ المهمة تعمل على تمديد المسار الحرج، يمكنك تأجيلها من خلال طلبها ببطء عندما تكون هناك حاجة إلى مخرجاتها فعليًا.

7. استخدام خوارزميات فعّالة في المدخلات الكبيرة

بالنسبة إلى الإدخالات الكبيرة، تصبح خوارزميات تعقيد الوقت الأمثل أمرًا بالغ الأهمية. لم ننظر في هذه الفئة في هذا المثال، ولكن لا يمكن المبالغة في أهميتها.

8. ميزة إضافية: قياس الأداء ضمن مسارك

للتأكد من أن التعليمات البرمجية المتطورة لا تزال سريعة، من المهم مراقبة السلوك ومقارنته بالمعايير. وبهذه الطريقة، يمكنك تحديد الانحدارات بشكل استباقي وتحسين الموثوقية بشكل عام، الأمر الذي يهيئك لتحقيق النجاح على المدى الطويل.