كيف يتم Node.js بشكل أسرع بطبيعتها عندما لا تزال تعتمد على المواضيع داخليًا؟

StackOverflow https://stackoverflow.com/questions/3629784

سؤال

لقد شاهدت للتو الفيديو التالي: مقدمة إلى Node.js وما زلت لا تفهم كيف تحصل على فوائد السرعة.

بشكل رئيسي ، في إحدى المراحل ، يقول Ryan Dahl (Node.js 'Creator) أن Node.js يعتمد على حلقة الحدث بدلاً من مؤشر الترابط. المواضيع باهظة الثمن ويجب تركها فقط لخبراء البرمجة المتزامنة ليتم استخدامها.

في وقت لاحق ، يعرض بعد ذلك كومة Architecture of Node.js التي لديها تطبيق C أساسي يحتوي على تجمع الخيوط الخاص به داخليًا. من الواضح أن مطوري Node.js لن يبدأوا خيوطهم الخاصة أبدًا أو يستخدمون تجمع الخيوط مباشرةً ... يستخدمون عوامل الاتصال غير المتزامنة. هذا الكثير أفهمه.

ما لا أفهمه هو النقطة التي لا يزال فيها node.js يستخدم مؤشرات الترابط ... إنه مجرد إخفاء التنفيذ ، فكيف يكون هذا أسرع إذا طلب 50 شخصًا 50 ملفًا (ليس في الذاكرة حاليًا) ، فلا مطلوب 50 سلسلة من مؤشرات الترابط ؟

الفرق الوحيد هو أنه نظرًا لأنه يتم إدارته داخليًا ، فإن مطور Node.js لا يتعين على ترميز التفاصيل الخيطية ولكن أسفله لا يزال يستخدم مؤشرات الترابط لمعالجة طلبات ملف IO (الحظر).

لذا ، ألا تتناول مشكلة واحدة فقط (الخيوط) وإخفائها أثناء وجود هذه المشكلة: بشكل أساسي عدة مؤشرات ترابط ، تبديل السياق ، القفزات الميتة ... إلخ؟

يجب أن يكون هناك بعض التفاصيل ما زلت لا أفهم هنا.

هل كانت مفيدة؟

المحلول

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

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

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

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

الآن ، إذا كنت ذكيًا حقًا ، فسوف تعبر عن الكود أعلاه بطريقة يمكن أن تنطلق فيها البيئة وتفعل شيئًا آخر أثناء تشغيل الاستعلام:

query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );

هذا هو في الأساس ما تفعله node.js. أنت تزين بشكل أساسي - بطريقة مريحة بسبب اللغة والبيئة ، ومن ثم النقاط المتعلقة بالإغلاق - الكود الخاص بك بطريقة يمكن أن تكون البيئة ذكية بشأن ما يدير ، ومتى. بهذه الطريقة ، Node.js ليست كذلك الجديد بمعنى أنه اخترع I/O غير المتزامن (ليس أن أي شخص ادعى أي شيء من هذا القبيل) ، لكنه جديد في الطريقة التي يتم التعبير عنها مختلفة بعض الشيء.

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

نصائح أخرى

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

إنه يستخدم المواضيع لأن:

  1. ال خيار O_Nonblock من Open () لا يعمل على الملفات.
  2. هناك مكتبات طرف ثالث لا تقدم IO غير المحظورة.

لتزوير IO غير المحظور ، الخيوط ضرورية: هل تمنع IO في موضوع منفصل. إنه حل قبيح ويسبب الكثير من النفقات العامة.

الأمر أسوأ على مستوى الأجهزة:

  • مع DMA وحدة المعالجة المركزية بشكل غير متزامن تفتح IO.
  • يتم نقل البيانات مباشرة بين جهاز IO والذاكرة.
  • يلتف kernel هذا في مكالمة نظام متزامنة حظر.
  • Node.js يلف استدعاء نظام الحظر في موضوع.

هذا مجرد غبي وغير فعال. لكنه يعمل على الأقل! يمكننا الاستمتاع بـ Node.js لأنه يخفي التفاصيل القبيحة والمرهقة وراء الهندسة المعمارية غير المتزامنة التي تعتمد على الحدث.

ربما يقوم شخص ما بتنفيذ O_Nonblock للملفات في المستقبل؟ ...

تعديل: ناقشت هذا مع صديق وأخبرني أن بديلًا للخيوط يتم استقصاء مع تحديد: حدد مهلة 0 وقم بإعداد IO على واصفات الملفات التي تم إرجاعها (الآن بعد ضمان عدم حظرها).

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

1) العنصر المعلق في الرمز الزائف في أحد الإجابات الشائعة

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

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

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

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

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

5) هناك طريقة أخرى أقبل بها منطق الادعاء بأن نهج العقدة "أسرع حسب التصميم" ، وهذا هو هذا. تستخدم معظم نماذج مؤشرات الترابط نموذج مفتاح السياق مرفقة بالوقت ، وطبقة أعلى من تنبيه الحكم الأكثر ملاءمة (تنبيه القيمة :) ونموذج أكثر كفاءة (وليس حكم قيمة). يحدث هذا لسببين ، أولاً ، لا يبدو أن معظم المبرمجين يفهمون الاستباق الأولوية ، وثانياً ، إذا تعلمت الخيوط في بيئة Windows ، فإن Timeslicing موجود سواء أعجبك ذلك أم لا (بالطبع ، هذا يعزز النقطة الأولى ؛ والجدير بالذكر أن الإصدارات الأولى من Java تستخدم الاستمتاع ذات الأولوية على تطبيقات Solaris ، و timeslising في Windows. نظرًا لأن معظم المبرمجين لم يفهموا ويشتكوا من أن "الخيوط لا تعمل في Solaris" قاموا بتغيير النموذج إلى انخفاض زمني في كل مكان). على أي حال ، فإن خلاصة القول هي أن Timeslicing يخلق مفاتيح سياق إضافية (وربما غير ضرورية). يستغرق كل مفتاح سياق وقت وحدة المعالجة المركزية ، ويتم إزالة ذلك الوقت بشكل فعال من العمل الذي يمكن القيام به في المهمة الحقيقية قيد التنفيذ. ومع ذلك ، يجب ألا يكون مقدار الوقت المستثمر في تبديل السياق بسبب TimeSling أكثر من نسبة مئوية صغيرة جدًا من الوقت الإجمالي ، ما لم يحدث شيء غريب إلى حد ما ، وليس هناك سبب يمكن أن أتوقع أن يكون هذا هو الحال في خادم ويب بسيط). لذا ، نعم ، فإن مفاتيح السياق الزائد المشاركة في TimeSling غير فعالة (وهذه لا تحدث في نواة المواضيع كقاعدة عامة ، راجع للشغل) ولكن الفرق سيكون بضعة في المئة من الإنتاجية ، وليس نوع العوامل العددية الكاملة التي تنطوي على مطالبات الأداء التي غالبا ما تكون ضمنية للعقدة.

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

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

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

هتاف ، توبي

ما لا أفهمه هو النقطة التي لا تزال Node.js تستخدم مؤشرات الترابط.

يستخدم Ryan مؤشرات الترابط لتلك الأجزاء التي يتم حظرها (معظم Node.js يستخدم IO غير المحظور) لأن بعض الأجزاء يصعب كتابة عدم الحظر. لكنني أعتقد أن رايان يرغب في الحصول على كل شيء غير محظور. تشغيل الشريحة 63 (التصميم الداخلي) ترى ريان يستخدم ليبيف(المكتبة التي تجذ إشعار الأحداث غير المتزامن) لعدم الحظر EventLoop. بسبب حدوث حلقة الحدث ، يحتاج JJs إلى مؤشرات ترابط أقل مما يقلل من تبديل السياق واستهلاك الذاكرة وما إلى ذلك.

تُستخدم المواضيع فقط للتعامل مع الوظائف التي لا تحتوي على منشأة غير متزامنة ، مثل stat().

ال stat() تعمل الوظيفة دائمًا ، لذا تحتاج Node.js إلى استخدام مؤشر ترابط لإجراء المكالمة الفعلية دون منع سلسلة الرسائل الرئيسية (حلقة الحدث). من المحتمل ، لن يتم استخدام أي مؤشر ترابط من تجمع الخيوط إذا لم تكن بحاجة إلى استدعاء هذا النوع من الوظائف.

لا أعرف شيئًا عن الأعمال الداخلية لـ Node.js ، لكن يمكنني أن أرى كيف يمكن استخدام حلقة حدث يمكن أن يتفوق على معالجة الإدخال/الإخراج الخيوط. تخيل طلب قرص ، أعطني staticfile.x ، اجعله 100 طلب لهذا الملف. يتناول كل طلب عادةً مؤشر ترابط إعادة تعزيز هذا الملف ، وهذا 100 مؤشر ترابط.

الآن تخيل الطلب الأول إنشاء مؤشر ترابط واحد يصبح كائن ناشر ، جميع الطلبات 99 أخرى تبدو أولاً إذا كان هناك كائن ناشر لـ StaticFile.x ، إذا كان الأمر كذلك ، استمع إليه أثناء قيامه بعمله ، وإلا ابدأ موضوعًا جديدًا ، وبالتالي أ كائن ناشر جديد.

بمجرد الانتهاء من الخيط المفرد ، يمرر StaticFile.x لجميع المستمعين 100 ويدمر نفسه ، وبالتالي فإن الطلب التالي ينشئ كائن جديد وناشر جديد.

لذلك فهو 100 مؤشر ترابط مقابل 1 مؤشر ترابط في المثال أعلاه ، ولكن أيضًا بحث واحد عن القرص بدلاً من 100 بحث عن القرص ، يمكن أن يكون المكسب ظاهريًا تمامًا. ريان رجل ذكي!

هناك طريقة أخرى للنظر إليها وهي أحد أمثلةه في بداية الفيلم. بدلاً من:

pseudo code:
result = query('select * from ...');

مرة أخرى ، 100 استعلامات منفصلة لقاعدة بيانات مقابل ...

pseudo code:
query('select * from ...', function(result){
    // do stuff with result
});

إذا كان الاستعلام مستمرًا بالفعل ، فإن الاستعلامات المتساوية الأخرى ستقفز ببساطة على العربة ، بحيث يمكنك الحصول على 100 استعلامات في قاعدة بيانات واحدة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top