أمثلية تطبيق node.js

نظرا: تطبيق http node.js القديم وزيادة الحمل عليه.

الحلول القياسية للمشكلة: قم بإنهاء الخوادم ، وإعادة كتابة كل شيء من 0 ، وتحسين ما تم كتابته بالفعل.

دعونا نحاول متابعة التحسين ومعرفة كيفية العثور على نقاط ضعف التطبيق وتحسينها. وربما تسريع دون لمس سطر واحد من التعليمات البرمجية :)

نرحب بجميع المهتمين تحت القط!

أولاً ، دعنا نقرر تقنية اختبار الأداء. سنهتم بعدد الطلبات المقدمة في ثانية واحدة: rps.

سنقوم بتشغيل التطبيق في الوضع 1 للعامل (عملية واحدة) ، وقياس أداء الشفرة القديمة والتعليمات البرمجية مع التحسينات - الأداء المطلق ليس مهمًا ، والأداء المقارن مهم.

في تطبيق نموذجي مع العديد من المسارات المختلفة ، من المنطقي أولاً العثور على الطلبات الأكثر تحميلًا ، والتي تستغرق معالجتها معظم الوقت. ستسمح لك الأدوات المساعدة مثل request-log-analizer أو العديد من الأدوات المشابهة باستخراج هذه المعلومات من السجلات.

من ناحية أخرى ، يمكنك الحصول على قائمة حقيقية بالطلبات ورقمهم جميعًا (على سبيل المثال ، باستخدام yandex-tank) - نحصل على ملف تعريف موثوق به.

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

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

تستخدم الخوادم المُباعة node.js الإصدار 6 - لنبدأ بها:

الطلبات / ثانية: 1210

نحاول في العقدة الثامنة:
الطلبات / ثانية: 2308
الملاحظة العاشرة:
الطلبات / ثانية: 2590

الفرق واضح. يتم لعب الدور الرئيسي هنا عن طريق تحديث إصدار v8 - كان الكثير من كود v8 المحسن بشكل سيئ في الماضي. ومن أجل عدم التعامل مع طواحين الهواء التي اختفت في node.js v8 ، من الأفضل الترقية على الفور ، ثم القيام بتحسين الشفرة.

ننتقل إلى البحث الفعلي عن الاختناقات: في رأيي ، فإن أفضل أداة لذلك هي flamegraph. ومع ظهور مشروع 0x ، كان الحصول على flamegraph بسيطًا جدًا - بدء 0x بدلاً من العقدة: 0x -o yourscript.js ، قم بإجراء اختبار ، أوقف البرنامج النصي ، انظر إلى النتيجة في المتصفح.

يبدو flamegraph للشفرة المختبرة شيئًا مثل هذا قبل التحسينات:


أسفل الفلاتر ، اترك التطبيق ، وإدخالات - فقط رمز التطبيق ووحدات الطرف الثالث.

كلما كان الشريط أوسع ، زاد الوقت الذي يقضيه في أداء هذه الوظيفة (بما في ذلك المكالمات المتداخلة).

سنتعامل مع الجزء المركزي الأكبر.

بادئ ذي بدء ، نسلط الضوء على الوظائف غير المحسنة. لقد وجدت عدد قليل من هذه في التطبيق.

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

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

نتيجة ملائمة لهذا النهج: يمكنك رؤية rps في ظروف مثالية (مع وظيفة بدء واحدة فقط ، rps قريبة من الحد الأقصى rps لتطبيق hode world node.js) ، ومع المزيد من حركة كعب الاستجابة في التطبيق ، لاحظ مساهمة الوظيفة المدروسة في انخفاض الأداء في آه في الثانية.

لذلك ، نترك فقط وظيفة البداية ، نحصل على:

الطلبات / ثانية: 16176



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

ننتقل إلى الوظيفة التالية:

الطلبات / ثانية: 16111
لم يتغير شيء - مزيد من الغطس:
الطلبات / ثانية: 13330


عملائنا! يمكن ملاحظة أن دالة getByUrl المعنية تحتل جزءًا كبيرًا من دالة البدء - والتي ترتبط بشكل جيد مع هبوط rps.

نحن ننظر بعناية إلى ما يحدث فيه (قم بتشغيل Core ، v8):

تحدث الكثير من الأشياء ... نحن ندخن الشفرة ونحسنها:

for (var i in this.data) { if (this[i]._options.regexp_obj.test(url)) return this[i]; } return null; 

تتحول إلى

 let result = null; for (let i=0; i<this.length && !result; i++) { if (this[i]._options.regexp_obj.test(url)) result = this[i]; } 

في هذه الحالة ، البساطة هي أسرع بكثير من .. في

احصل على طلبات / ثانية: 16015



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

ننتقل إلى الوظيفة التالية.

الطلبات / ثانية: 13316



تحتوي هذه الوظيفة على الكثير من وظائف الصفيف ، وعلى الرغم من التسارع الكبير في الإصدارات الأخيرة من node.js ، إلا أنها لا تزال أبطأ من الحلقات البسيطة: change [] .map and filter. بانتظام للحصول على

الطلبات / ثانية: 15067



وهكذا مرة بعد مرة ، لكل وظيفة لاحقة.

بعض التحسينات المفيدة الأخرى: بالنسبة للتجزئة مع مجموعة مفاتيح متغيرة ديناميكيًا ، يمكن أن تكون الخريطة الجديدة () أسرع بنسبة 40٪ من العادية {} ؛

Math.round (el * 100) / 100 أسرع مرتين من toFixed (2).

في flamegraph للوظائف الأساسية و v8 ، يمكنك رؤية كل من الإدخالات الغامضة و StringPrototypeSplit الناطقة تمامًا أو v8 :: internal :: Runtime_StringToNumber ، وإذا كان هذا جزءًا مهمًا من تنفيذ التعليمات البرمجية ، فحاول تحسين ، على سبيل المثال ، ببساطة إعادة كتابة التعليمات البرمجية التي لا تنفذ هذه العمليات.

على سبيل المثال ، يمكن أن يؤدي استبدال الانقسام بمكالمات indexOf و substring المتعددة إلى مضاعفات أداء مضاعفة.

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

يمكن أن تساعد دراسة مدروسة لمخرجات العقدة --trace_file_names --trace_opt_verbose --trace-deopt --trace_opt هنا.

على سبيل المثال ، خطوط النموذج

deoptimizing (DEOPT soft): ابدأ 0x2bcf38b2d079 <وظيفة JS getTime ... تعليقات غير كافية من النوع للتشغيل الثنائي أدت إلى الخط

عودة فال> = 10؟ فال: '0' + فال ؛

استبدال

return (val> = 10؟ '': '0') + val ؛

صحح الوضع.

هناك الكثير من المعلومات لمحرك V8 القديم لأسباب وطرق مكافحة إلغاء الوظائف الوظيفية:

github.com/P0lip/v8-deoptimize-reasons - قائمة ،
www.netguru.co/blog/tracing-patterns-hinder-performance - تحليل الأسباب النموذجية ،
www.html5rocks.com/en/tutorials/speed/v8 - حول تحسينات الإصدار 8 ، أعتقد أن الشيء نفسه ينطبق على محرك الإصدار 8 الحالي.

لكن العديد من المشاكل لم تعد ذات صلة بالإصدار 8 الجديد.

على أي حال ، بعد كل التحسينات ، تمكنت من الحصول على الطلبات / ثانية: 9971 ، أي ستسرع مرتين تقريبًا بسبب الانتقال إلى أحدث إصدار من node.js ، وأربع مرات أخرى بسبب تحسين الشفرة.

آمل أن تكون هذه التجربة مفيدة لشخص آخر.

Source: https://habr.com/ru/post/ar414541/


All Articles