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

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

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

يرشدك المثال التالي إلى كيفية استخدام لوحة الأداء.

إعداد سيناريو وضع العلامات وإعادة إنشائه

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

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

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

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

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

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

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

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

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

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

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

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

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

لقطة شاشة لواجهة لوحة الأداء في أدوات مطوّري البرامج وهي تتحقّق من نسخة أخرى من لوحة الأداء تستغرق المهمة المرتبطة بالدالة buildProfileCalls حوالي 0.5 ثانية.

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

لقطة شاشة لملفات تعريف الذاكرة في أدوات مطوّري البرامج التي تقيِّم استهلاك الذاكرة في لوحة الأداء يشير المفتش إلى أنّ دالة buildProfileCalls هي المسؤولة عن تسرُّب الذاكرة.

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

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

تعرض لقطة الشاشة التالية لقطة الذاكرة المحجوزة التي تم جمعها.

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

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

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

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

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

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

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

/**
 * 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 استغرق تشغيل الدالة 1,372.51 ملي ثانية.

بعد:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

الخاتمة

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

قبل:

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

بعد:

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

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

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

الخلاصات

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

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

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

استخدِم عيّنات يمكن استخدامها كأحمال عمل تمثيلية واطّلِع على النتائج التي يمكنك العثور عليها.

2. تجنَّب التسلسلات الهرميّة المعقدة للمكالمات.

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

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

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

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

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

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

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

6- تأجيل العمل غير المهم

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

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

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

8. مكافأة: قياس أداء مسارات الإحالات الناجحة

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