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

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

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

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

إعداد سيناريو إنشاء الملفات الشخصية وإعادة إنشائه

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

القسم الثالث غريب بعض الشيء: يمكنك أن ترى في الرسم البياني الشعلة أنّه يتألف من أعمدة ضيقة ولكنها طويلة، ما يشير إلى استدعاءات دالة عميقة، وتكرارات عميقة في هذه الحالة. في المجمل، استغرق هذا القسم حوالي 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. مكافأة: قياس أداء مسارات المبيعات

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