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

قياس سرعة التطبيق
في محاولة لتحسين تنميتنا ، قضينا الكثير من الوقت في قياس سرعته. وكما اتضح فيما بعد ، فإن مقاييس الأداء هي مؤشرات يصعب فهمها وتطبيقها بشكل مدهش.
فمن ناحية ، يصعب تصميم مقاييس تصف بدقة الأحاسيس التي يواجهها المستخدم أثناء العمل مع النظام. من ناحية أخرى ، ليس من السهل إنشاء مقاييس دقيقة للغاية بحيث يسمح لك تحليلها باتخاذ قرارات مستنيرة. نتيجة لذلك ، لا يمكن للعديد من فرق التطوير الوثوق في البيانات التي يجمعونها حول أداء مشاريعهم.
حتى إذا كان لدى المطورين مقاييس موثوقة ودقيقة ، فإن استخدامها ليس بالأمر السهل. كيفية تحديد مصطلح "سريع"؟ كيف تجد التوازن بين السرعة والاتساق؟ كيفية تعلم اكتشاف تدهور الأداء بسرعة أو تعلم تقييم تأثير التحسينات على النظام؟
نريد هنا مشاركة بعض الأفكار فيما يتعلق بتطوير أدوات تحليل أداء تطبيقات الويب.
1. باستخدام "الساعة" الصحيحة
يحتوي JavaScript على آليتين لاسترداد الطوابع الزمنية:
performance.now()
و
new Date()
.
كيف تختلف؟ الاختلافات التالية جهازي أساسية بالنسبة لنا:
- الأسلوب
performance.now()
أكثر دقة بكثير. دقة بنية new Date()
هي ms 1 مللي ثانية ، في حين أن دقة performance.now()
هي بالفعل ± 100 (s (نعم ، إنها تتعلق بالميكرو ثانية !). - دائماً ما يتم زيادة القيم التي يتم إرجاعها بواسطة الأسلوب
performance.now()
بمعدل ثابت وتكون مستقلة عن وقت النظام. هذه الطريقة ببساطة تقيس الفواصل الزمنية دون التركيز على وقت النظام. وفي new Date()
يؤثر وقت النظام. إذا قمت بإعادة ترتيب ساعة النظام ، فسيؤدي ذلك أيضًا إلى تغيير ما يعيد new Date ()
، وسيؤدي ذلك إلى تدمير بيانات مراقبة الأداء.
على الرغم من أن "الساعات" التي تمثلها طريقة
performance.now()
من الواضح أنها أكثر ملاءمة لقياس الفواصل الزمنية ، إلا أنها ليست مثالية أيضًا. يعاني كل من
performance.now()
و
new Date()
من نفس المشكلة ، والتي تظهر في حالة أن النظام في حالة من النوم: تشمل القياسات الوقت الذي لم يكن فيه الجهاز نشطًا.
2. التحقق من نشاط التطبيق
إذا كنت تقيس أداء تطبيق ويب ، فانتقل من علامة تبويبها إلى علامة تبويب أخرى - فهذا سيعطل عملية جمع البيانات. لماذا؟ الحقيقة هي أن المتصفح يقيد التطبيقات الموجودة في علامات تبويب الخلفية.
هناك حالتان يمكن تشويه المقاييس فيهما. نتيجة لذلك ، سيبدو التطبيق أبطأ بكثير مما هو عليه بالفعل.
- يذهب الكمبيوتر إلى وضع السكون.
- يعمل التطبيق في علامة تبويب الخلفية في المتصفح.
حدوث كل من هذه الحالات ليست غير شائعة. لحسن الحظ ، لدينا خياران لحلها.
أولاً ، يمكننا ببساطة تجاهل المقاييس المشوهة ، وتجاهل نتائج القياس التي تختلف كثيرًا عن بعض القيم المعقولة. على سبيل المثال ، لا يمكن تنفيذ التعليمات البرمجية التي تسمى عند الضغط على زر لمدة 15 دقيقة! ربما هذا هو الشيء الوحيد الذي تحتاجه من أجل التعامل مع المشكلتين الموصوفتين أعلاه.
ثانياً ، يمكنك استخدام خاصية
document.hidden
وحدث
visibilitychange . يتم رفع حدث
visibilitychange
عندما ينتقل المستخدم من علامة تبويب المتصفح المهمة إلى علامة تبويب أخرى أو يعود إلى علامة التبويب التي تهمنا. يتم استدعاؤه عندما تقل نافذة المتصفح أو تزيد عندما يبدأ الكمبيوتر في العمل ، ويخرج من وضع السكون. بمعنى آخر ، هذا هو بالضبط ما نحتاجه. بالإضافة إلى ذلك ، طالما كانت علامة التبويب في الخلفية ، تكون الخاصية
document.hidden
true
.
فيما يلي مثال بسيط يوضح استخدام خاصية
document.hidden
وحدث
visibilitychange
.
let lastVisibilityChange = 0 window.addEventListener('visibilitychange', () => { lastVisibilityChange = performance.now() }) // , , // , , if (metric.start < lastVisibilityChange || document.hidden) return
كما ترون ، نحن نتجاهل بعض البيانات ، لكن هذا جيد. الحقيقة هي أن هذه هي البيانات المتعلقة بتلك الفترات من البرنامج عندما لا يمكن استخدام موارد النظام بشكل كامل.
تحدثنا الآن عن المؤشرات التي لا تهمنا. ولكن هناك العديد من الحالات ، البيانات التي تم جمعها والتي هي مثيرة للاهتمام للغاية بالنسبة لنا. دعونا ننظر في كيفية جمع هذه البيانات.
3. ابحث عن المؤشر الذي يسمح لك بالتقاط أفضل وقت لبدء الحدث
واحدة من أكثر ميزات جافا سكريبت إثارة للجدل هي أن حلقة الحدث لهذه اللغة مترابطة. في وقت معين ، لا يمكن تنفيذ سوى جزء واحد من التعليمات البرمجية ، ولا يمكن مقاطعة تنفيذه.
إذا ضغط المستخدم على الزر أثناء تنفيذ رمز معين ، فلن يعرف البرنامج عنه حتى يتم الانتهاء من تنفيذ هذا الرمز. على سبيل المثال ، إذا أنفق التطبيق 1000 مللي ثانية في دورة مستمرة ، وضغط المستخدم على الزر
Escape
100 مللي ثانية بعد بداية الدورة ، فلن يتم تسجيل الحدث لمدة 900 مللي ثانية.
هذا يمكن أن تشوه بشدة المقاييس. إذا كنا بحاجة إلى الدقة في قياس كيفية إدراك المستخدم للعمل مع البرنامج ، فهذه مشكلة كبيرة!
لحسن الحظ ، حل هذه المشكلة ليس بالأمر الصعب. إذا كنا نتحدث عن الحدث الحالي ، فيمكننا ، بدلاً من استخدام
performance.now()
(الوقت الذي رأينا فيه الحدث) ، استخدام
window.event.timeStamp
(الوقت الذي تم فيه إنشاء الحدث).
يتم تعيين الطابع الزمني للحدث بواسطة عملية المتصفح الرئيسية. نظرًا لأن هذه العملية لا تمنع عندما تكون حلقة أحداث JS مؤمنة ، فإن
event.timeStamp
يعطينا معلومات أكثر قيمة حول وقت إطلاق الحدث بالفعل.
تجدر الإشارة إلى أن هذه الآلية ليست مثالية. لذلك ، بين اللحظة التي يتم فيها الضغط على الزر الفعلي واللحظة التي يصل فيها الحدث المقابل إلى Chrome ، تنقضي 9-15 مللي ثانية من الوقت غير المحسوب (
هنا مقالة ممتازة يمكنك من خلالها معرفة سبب حدوث ذلك).
ومع ذلك ، حتى لو استطعنا قياس الوقت الذي يستغرقه الحدث للوصول إلى Chrome ، فلا ينبغي لنا أن ندرج هذه المرة في مقاييسنا. لماذا؟ الحقيقة هي أنه لا يمكننا إدخال مثل هذه التحسينات في الشفرة التي يمكن أن تؤثر بشكل كبير على مثل هذه التأخيرات. لا يمكننا تحسينها بأي شكل من الأشكال.
نتيجة لذلك ، إذا تحدثنا عن العثور على الطابع الزمني لبداية الحدث ، فإن مؤشر
event.timeStamp
يبدو أكثر ملاءمة هنا.
ما هو أفضل تقدير لموعد انتهاء الحدث؟
4. قم بإيقاف تشغيل المؤقت في requestAnimationFrame ()
إحدى النتائج الأخرى تأتي من ميزات جهاز حلقة الحدث في JavaScript: قد يتم تنفيذ بعض التعليمات البرمجية غير المرتبطة بالرمز بعد ذلك ، ولكن قبل أن يعرض المستعرض إصدارًا محدثًا من الصفحة على الشاشة.
النظر ، على سبيل المثال ، رد فعل. بعد تنفيذ التعليمات البرمجية الخاصة بك ، React بتحديث DOM. إذا قمت بقياس الوقت في الكود الخاص بك فقط ، فهذا يعني أنك لن تقيس الوقت الذي استغرقته في تنفيذ React code.
لقياس هذا الوقت الإضافي ، نستخدم
requestAnimationFrame()
لإيقاف الموقت. يتم ذلك فقط عندما يكون المستعرض جاهزًا لإخراج الإطار التالي.
requestAnimationFrame(() => { metric.finish(performance.now()) })
هذه هي دورة حياة الإطار (الرسم مأخوذ من
هذه المادة الرائعة عند
requestAnimationFrame
).
دورة حياة الإطاركما ترى في هذا الشكل ، يتم استدعاء
requestAnimationFrame()
بعد اكتمال المعالج ، مباشرة قبل عرض الإطار. إذا أوقفنا الموقت هنا ، فهذا يعني أنه يمكننا التأكد تمامًا من أن كل شيء استغرق وقتًا لتحديث الشاشة يتم تضمينه في البيانات التي تم جمعها على الفاصل الزمني.
جيد جدًا ، لكن الوضع أصبح معقدًا إلى حد ما ...
5. تجاهل الوقت اللازم لإنشاء تخطيط الصفحة وتصورها.
يوضح المخطط السابق ، الذي يوضح دورة حياة الإطار ، مشكلة أخرى واجهناها. في نهاية دورة حياة الإطار ، هناك كتل تخطيط (تشكل تخطيط صفحة) وطلاء (عرض صفحة). إذا لم تأخذ في الاعتبار الوقت اللازم لإكمال هذه العمليات ، فسيكون الوقت الذي نقيسه أقل من الوقت الذي تستغرقه بعض البيانات المحدثة لتظهر على الشاشة.
لحسن الحظ ، فإن
requestAnimationFrame
لديه آس آخر. عندما
requestAnimationFrame
استدعاء الدالة التي تم تمريرها بواسطة
requestAnimationFrame
، يتم تمرير طابع زمني إلى هذه الوظيفة ، يشير إلى وقت بدء تكوين الإطار الحالي (أي ، الموجود في الجزء الأيسر للغاية من الرسم التخطيطي الخاص بنا). يكون هذا الطابع الزمني قريبًا جدًا من وقت انتهاء الإطار السابق.
نتيجة لذلك ، يمكن تصحيح العيب المذكور أعلاه عن طريق قياس إجمالي الوقت المنقضي من لحظة
event.timeStamp
إلى وقت بداية تشكيل الإطار التالي. لاحظ الطلب المتداخل
requestAnimationFrame
:
requestAnimationFrame(() => { requestAnimationFrame((timestamp) => { metric.finish(timestamp) }) })
على الرغم من أن ما هو موضح أعلاه يبدو كحل ممتاز للمشكلة ، إلا أننا قررنا في النهاية عدم استخدام هذا التصميم. الحقيقة هي أنه على الرغم من أن هذه التقنية تجعل من الممكن الحصول على بيانات أكثر موثوقية ، إلا أن دقة هذه البيانات تقل. تتشكل الإطارات في Chrome بتردد 16 مللي ثانية. هذا يعني أن أعلى دقة متوفرة لنا هي ms 16 مللي ثانية. وإذا كان المتصفح مكتظًا ويتخطى الإطارات ، فستكون الدقة أقل ، وسيكون هذا التدهور غير متوقع.
إذا قمت بتطبيق هذا الحل ، فلن يؤثر تحسن كبير في أداء التعليمات البرمجية الخاصة بك ، مثل تسريع مهمة تم تنفيذها مسبقًا 32 مللي ثانية ، وحتى 15 مللي ثانية ، على نتائج قياس الأداء.
مع الأخذ في الاعتبار الوقت اللازم لإنشاء تخطيط صفحة ومخرجاته ، نحصل على مقاييس أكثر دقة (± 100 )s) للرمز الموجود تحت سيطرتنا. نتيجةً لذلك ، يمكننا الحصول على تعبير رقمي عن أي تحسين يتم إجراؤه على هذا الرمز.
لقد استكشفنا أيضًا فكرة مماثلة:
requestAnimationFrame(() => { setTimeout(() => { metric.finish(performance.now()) } })
سيشمل ذلك وقت العرض ، لكن دقة المؤشر لن تقتصر على ms 16 مللي ثانية. ومع ذلك ، قررنا عدم استخدام هذا النهج أيضا. إذا واجه النظام حدث إدخال طويل ، فإن الدعوة إلى إرسال
setTimeout
يمكن أن تتأخر وتنفذ بشكل كبير بعد تحديث واجهة المستخدم.
6. توضيح "النسبة المئوية للأحداث التي تقل عن الهدف"
نحن نطور مشروعًا ونركز على الأداء العالي ، ونحاول تحسينه بطريقتين:
- السرعة. يجب أن يكون وقت تنفيذ المهمة الأسرع أقرب وقت ممكن إلى 0 مللي ثانية.
- التوحيد. يجب أن يكون وقت تنفيذ المهمة الأبطأ أقرب وقت ممكن من تنفيذ المهمة الأسرع.
بسبب حقيقة أن هذه المؤشرات تتغير مع مرور الوقت ، فمن الصعب تصورها وليس من السهل مناقشتها. هل من الممكن إنشاء نظام لتصور مثل هذه المؤشرات التي من شأنها أن تلهمنا لتحسين كل من السرعة والتوحيد؟
نهج نموذجي لقياس المئين 90 من التأخير. تسمح لك هذه الطريقة برسم رسم بياني خطي على طول المحور Y حيث يتم حفظ الوقت بالميلي ثانية. يتيح لك هذا الرسم البياني أن ترى أن 90٪ من الأحداث تقع أسفل الرسم البياني للخط ، أي أنها تنفذ بشكل أسرع من الوقت الذي يشير إليه الرسم البياني للخط.
من المعروف أن
100 مللي ثانية هي الحدود بين ما يُنظر إليه على أنه "سريع" و "بطيء".
ولكن ماذا سنعرف كيف يشعر المستخدمون من العمل إذا علمنا أن النسبة المئوية 90 من التأخير هي 103 مللي ثانية؟ ليس بشكل خاص ما هي المؤشرات التي ستوفر للمستخدمين سهولة الاستخدام؟ لا توجد وسيلة لمعرفة هذا بالتأكيد.
ولكن ماذا لو علمنا أن النسبة المئوية للتأخير هي 93 مللي ثانية؟ هناك شعور بأن 93 أفضل من 103 ، ولكن لا يمكننا قول أي شيء أكثر عن هذه المؤشرات ، وكذلك ما يعنيه من حيث تصور المستخدم للمشروع. مرة أخرى ، لا توجد إجابة دقيقة على هذا السؤال.
لقد وجدنا حلا لهذه المشكلة. وهو يتألف من قياس النسبة المئوية للأحداث التي لا تتجاوز مدة تنفيذها 100 مللي ثانية. هناك ثلاث مزايا كبيرة لهذا النهج:
- المقياس موجه نحو المستخدم. يمكنها أن تخبرنا ما هي النسبة المئوية من الوقت الذي يتم فيه تطبيقنا بسرعة ، وما هي النسبة المئوية للمستخدمين الذين يرون أنه سريع.
- يسمح لنا هذا المقياس بإعادة القياسات إلى الدقة التي ضاعت نظرًا لحقيقة أننا لم نقيس الوقت المستغرق لإكمال المهام في نهاية الإطار (تحدثنا عن ذلك في القسم رقم 5). نظرًا لحقيقة أننا قمنا بتعيين مؤشر مستهدف يلائم عدة إطارات ، فإن نتائج القياس القريبة من هذا المؤشر إما أن تكون أقل من ذلك أو أكثر.
- هذا المقياس أسهل في الحساب. يكفي حساب عدد الأحداث التي يكون وقت تنفيذها أقل من المؤشر المستهدف ، وبعد ذلك - يتم تقسيمها على إجمالي عدد الأحداث. النسب المئوية أصعب بكثير. هناك تقديرات تقريبية فعالة ، ولكن لكي تفعل كل شيء بشكل صحيح ، عليك أن تأخذ في الاعتبار كل بعد.
يحتوي هذا النهج على ناقص واحد: إذا كانت المؤشرات أسوأ من الهدف ، فلن يكون من السهل ملاحظة تحسنها.
7. استخدام عدة قيم عتبة في تحليل المؤشرات
من أجل تصور نتيجة تحسين الأداء ، أدخلنا العديد من قيم العتبة الإضافية في نظامنا - أعلى من 100 مللي ثانية أو أقل.
قمنا بتجميع التأخيرات مثل هذا:
- أقل من 50 مللي ثانية (سريع).
- 50 إلى 100 مللي ثانية (جيد).
- 100 إلى 1000 مللي ثانية (بطيئة).
- أكثر من 1000 مللي ثانية (بطيئة بشكل رهيب).
نتائج "بطيئة بشكل رهيب" تسمح لنا أن نرى أننا قد فاتنا مكان ما كثيرا. لذلك ، نحن نسلط الضوء عليها باللون الأحمر الساطع.
ما يناسب 50 مللي ثانية حساس للغاية للتغييرات. هنا ، غالبًا ما تكون تحسينات الأداء مرئية قبل فترة طويلة من رؤيتها في مجموعة تتوافق مع 100 مللي ثانية.
على سبيل المثال ، يوضح الرسم البياني التالي أداء عرض مؤشر الترابط في Superhuman.
عرض الموضوعفإنه يدل على فترة انخفاض الأداء ، ثم - نتائج التحسينات. من الصعب تقييم انخفاض الأداء إذا نظرت فقط إلى المؤشرات المقابلة لـ 100 مللي ثانية (الأجزاء العليا من الأعمدة الزرقاء). عند النظر إلى النتائج التي تتناسب مع 50 مللي ثانية (الأجزاء العليا من الأعمدة الخضراء) ، تكون مشاكل الأداء مرئية بالفعل بشكل أكثر وضوحًا.
إذا استخدمنا النهج التقليدي لدراسة مقاييس الأداء ، فربما لن نلاحظ مشكلة يظهر تأثيرها على النظام في الشكل السابق. ولكن بفضل الطريقة التي نأخذ بها القياسات والطريقة التي نتصور بها مقاييسنا ، تمكنا من إيجاد مشكلة بسرعة وحلها.
النتائج
اتضح أنه كان من الصعب جدًا العثور على النهج الصحيح للعمل مع مقاييس الأداء. تمكنا من تطوير منهجية تسمح لنا بإنشاء أدوات عالية الجودة لقياس أداء تطبيقات الويب. وهي نتحدث عن ما يلي:
- يتم قياس وقت بدء الحدث باستخدام
event.timeStamp
. - يتم قياس وقت نهاية الحدث باستخدام
performance.now()
في رد الاتصال الذي تم تمريره إلى requestAnimationFrame()
. - يتم تجاهل كل ما يحدث مع التطبيق أثناء وجوده في علامة تبويب المتصفح غير النشطة.
- يتم تجميع البيانات باستخدام مؤشر ، يمكن وصفه بأنه "النسبة المئوية للأحداث التي تقل عن الهدف".
- يتم تصور البيانات باستخدام عدة مستويات من قيم العتبة.
تمنحك هذه التقنية الأدوات اللازمة لإنشاء مقاييس موثوقة ودقيقة. يمكنك إنشاء رسومات بيانية تُظهر انخفاضًا واضحًا في الأداء ، ويمكنك تصور نتائج التحسينات. والأهم من ذلك - لديك الفرصة لجعل مشاريع سريعة بشكل أسرع.
أعزائي القراء! كيف يمكنك تحليل أداء تطبيقات الويب الخاصة بك؟
