لوحة أداء أسرع بنسبة 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 تختلف عن درج "الذاكرة" في لوحة الأداء) للتحقيق في الأمر. ضمن لوحة "الذاكرة"، تم اختيار نوع التحليل "لعينة التخصيص"، والذي سجّل لقطة لأجزاء من الذاكرة للوحة الأداء التي تُحمِّل الملف الشخصي لوحدة المعالجة المركزية (CPU).

لقطة شاشة للحالة الأولية لأداة تحليل الذاكرة يتم تمييز خيار "تحليل عيّنات عمليات التخصيص" بمربّع أحمر، ويشير ذلك إلى أنّ هذا الخيار هو الأفضل لتحليل ذاكرة 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 ثانية!

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

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

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

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

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

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

الخاتمة

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

قبل:

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

بعد:

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

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

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

الخلاصات

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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