التضمين هو أحد أهم التحسينات في المترجمات. إنه لا يزيل الحمل الزائد من المكالمة فحسب ، بل يفتح أيضًا العديد من الاحتمالات للتحسينات الأخرى ، على سبيل المثال ، الطي المستمر ، وإزالة الرمز الميت ، وما إلى ذلك. علاوة على ذلك ، يؤدي التضمين أحيانًا إلى انخفاض في حجم وظيفة الاستدعاء! سألت عدة أشخاص عما إذا كانوا يعرفون ما هي القواعد التي يتم تضمينها في الدالات الموجودة في C # ، وأجاب معظمهم بأن JIT ينظر إلى حجم رمز IL ويضمن فقط الوظائف الصغيرة بحجم يصل إلى 32 بايت على سبيل المثال. لذلك ، قررت كتابة هذا المنشور للكشف عن تفاصيل التنفيذ بمساعدة مثل هذا المثال ، والذي سيظهر العديد من الاستدلال في العمل في وقت واحد:
هل تعتقد أن المكالمة إلى مُنشئ الحجم سيتم تضمينها هنا؟ من الواضح أنه لا. إنه كبير جدًا ، خاصةً بسبب الوزن الثقيل الذي يلقي المشغلين الجدد ، مما يؤدي إلى تشفير جريء إلى حد ما. دعونا تحقق في Disasmo:
مضمنة! علاوة على ذلك ، تم حذف جميع الاستثناءات وفروعها بنجاح! يمكنك أن تقول شيئًا بأسلوب "آه ، حسنًا ، جيت ذكي للغاية وقام بتحليل كامل لجميع المرشحين للتضمين ، ونظر في ما سيحدث إذا اجتازت حجج معينة" أو "يحاول jit تضمين كل ما هو ممكن ، ويؤدي كل التحسينات ، ثم يقرر بشكل مربح أم لا "(التقط التوافقات وحساب تعقيد هذه العملية ، على سبيل المثال ، لرسم بياني استدعاء لعشر أو طريقتين).حسنًا ... لا ، هذا غير واقعي ، خاصة من حيث الوقت المناسب. لذلك ، يستخدم معظم المترجمين ما يسمى الملاحظات والاستدلال لحل مشكلة حقيبة الظهر الكلاسيكية هذه ومحاولة تحديد ميزانيتهم الخاصة وتناسبها بأكبر قدر ممكن من الكفاءة (ولا ، PGO ليست دواء لكل داء). لدى RyuJIT ملاحظات إيجابية وسلبية. زيادة معامل الاستحقاق (مضاعف المنافع). كلما زاد المعامل ، زاد عدد الرموز التي يمكننا تضمينها. الملاحظات السلبية على العكس - خفضه أو حتى حظر التضمين. دعونا نرى ما هي الملاحظات التي قام بها RyuJIT لمثالنا:
يمكن مشاهدة هذه الملاحظات في السجلات من COMPlus_JitDump (على سبيل المثال ، في Disasmo):
كل هذه الملاحظات البسيطة زادت المعامل من 1.0إلى 11.5 وساعد في التغلب على ميزانية المضمن ، على سبيل المثال ، حقيقة أننا نجتاز حجة مستمرة ومقارنتها مع ثابت آخر يخبرنا أنه مع درجة عالية من الاحتمال بعد انهيار الثوابت ، سيتم حذف أحد الفروع الشرطية وسيصبح الرمز أصغر. أو ، على سبيل المثال ، حقيقة أن هذا هو منشئ ويسمى داخل الحلقة هو أيضا تلميح إلى jit أنه يجب أن يخفف متطلبات التضمين.بالإضافة إلى مضاعف الفائدة ، يستخدم RyuJIT أيضًا الملاحظات للتنبؤ بحجم رمز الوظيفة الأصلية وتأثير أدائه باستخدام الثوابت السحرية في EstimateCodeSize () و EstimatePerformanceImpact () التي تم الحصول عليها باستخدام ML.بالمناسبة ، هل لاحظت هذه الحيلة؟:if ((value - 'A') > ('Z' - 'A'))
هذه نسخة محسنة من أجل:if (value < 'A' || value > 'Z')
كلا التعبيرين متماثلان ، ولكن في الحالة الأولى لدينا وحدة قاعدة واحدة ، وفي الثانية هناك ثلاثة منها. اتضح أن Inliner لديها حد صارم على عدد الكتل الأساسية في الوظيفة وإذا تجاوزت 5 ، فلا يهم حجم مضاعف مصلحتنا - يتم إلغاء التضمين. لذلك قمت بتطبيق هذه الخدعة لتناسب هذا المطلب الصارم. سيكون من الرائع إذا فعلت روسلين ذلك من أجلي.المسألة في روزلين : github.com/dotnet/runtime/issues/13347العلاقات العامة في RyuJIT (محاولتي حرج): github.com/dotnet/coreclr/pull/27480هناك وصفت مثال لماذا كان من المنطقي أن تفعل ليس فقط في جيت ولكن وفي المترجم C #.الأساليب المضمنة والافتراضية
كل شيء واضح هنا ، لا يمكنك تضمين ما لا توجد معلومات عنه في مرحلة التجميع ، على الرغم من أن النوع أو الطريقة مختومة ، فلماذا لا .ضمانة ورمي استثناءات
إذا كانت إحدى الطرق لا تُرجع أبدًا قيمة (على سبيل المثال ، فقط throw new
...) ، فعندئذٍ يتم وضع علامة على هذه الأساليب تلقائيًا على أنها أدوات مساعدة في الرمي ولن يتم تضمينها. هذه طريقة لمسح الكود المعقد من throw new
تحت السجادة واسترضاء البطانة.السمة الضمنية [AggressiveInlining]
في هذه الحالة ، تنصح بأن يكون الخط مضمنًا في الطريقة ، ولكن يجب أن تكون شديد الحذر لسببين:- ربما تقوم بتحسين حالة واحدة وتفاقم كل الحالات الأخرى (على سبيل المثال ، تحسين حالة الحجج الثابتة) حسب حجم الكود.
- غالبًا ما يولد التضمين عددًا كبيرًا من المتغيرات المؤقتة التي يمكن أن تتجاوز حدًا معينًا - عدد المتغيرات التي يمكن أن تتتبع دورة حياتها RyuJIT (512) وبعد ذلك سيبدأ الرمز في النمو إلى انسكابات رهيبة على المكدس ويتباطأ بشكل كبير. مثالان جيدان : tyts و tyts .
الأساليب المبطنة والديناميكية
في الوقت الحالي ، لا يتم تضمين هذه الأساليب ولا يتم تضمينها في نفسها: github.com/dotnet/runtime/issues/34500محاولتي لكتابة ارشادي
في الآونة الأخيرة ، حاولت كتابة الاستدلال الخاص بك للمساعدة هنا مثل هذه المناسبة:
في آخر مشاركة ذكرت أنني قمت بتحسين حساب RyuJIT لطول السلاسل الثابتة ( "Hello".Length -> 5
نرى أنه إذا كانت zainlaynit) ، وهكذا ، في المثال أعلاه ^ Validate
في Test
، نحصل if ("hello".Length > 10)
ما هو الأمثل في if (5 > 10)
ما هو الأمثل في إزالة الشرط / الفرع بأكمله. ومع ذلك ، رفضت شركة Inliner التضمين Validate
:
والمشكلة الرئيسية هنا هي أنه لا يوجد دليل استرشادي حتى الآن يخبر Jit أننا نمرر سلسلة ثابتة إليه System.String::get_Length
، مما يعني أن Callvirt-call سوف تنهار على الأرجح إلى ثابت وسيتم حذف الفرع بأكمله. في الواقع ، ارشاديويضيف هذه الملاحظة (ناقص الوحيد هو أنه يجب عليك حل جميع callvirts ، وهو ليس سريعًا جدًا).هناك قيود أخرى ، يمكن العثور على قائمة بها بشكل عام هنا . وهنا يمكنك أن تقرأ الأفكار واحد من المطورين JIT الرئيسي حول تصميم inliner و مقالته على استخدام آلة التعلم لهذه القضية.