قد يؤدي استخدام نوع الهيكل ومُعدِّل القراءة فقط إلى تدهور الأداء في بعض الأحيان. سنتحدث اليوم عن كيفية تجنب ذلك باستخدام محلل رمز مفتوح المصدر واحد - ErrorProne.NET.

كما تعلم على الأرجح من
مطبوعاتي السابقة "
المُعدِّل" والبُنى المقروءة فقط في C # "(" المُعدِّل في البنى المُقروءة في C # ") و"
اعتراضات الأداء للسكان المحليين المُرجعين والعودة المُرجعة في C # "(" اعتراضات الأداء عند استخدام المتغيرات المحلية وقيم الإرجاع مع معدّل المرجع)) ، العمل مع الهياكل أكثر صعوبة مما قد يبدو. إذا تركنا جانباً مسألة قابلية التغيير ، ألاحظ أن سلوك الهياكل ذات معدل القراءة فقط (للقراءة فقط) وبدونها في سياقات للقراءة فقط يختلف اختلافًا كبيرًا.
من المفترض أن يتم استخدام الهياكل في البرامج النصية للبرمجة التي تتطلب أداءً عاليًا ، ولعملها بشكل فعال ، يجب أن تعرف شيئًا عن العمليات المخفية المختلفة التي تم إنشاؤها بواسطة المحول البرمجي لضمان بقاء الهيكل دون تغيير.
فيما يلي قائمة مختصرة بالتنبيهات التي يجب أن تتذكرها:
- يمكن أن يتسبب استخدام الهياكل الكبيرة التي يتم تمريرها أو إرجاعها حسب القيمة في حدوث مشكلات في الأداء في مسارات تنفيذ البرنامج الهامة.
- يتسبب
xY
إنشاء نسخة واقية من x إذا:
x
هو حقل للقراءة فقط ؛- النوع
x
عبارة عن هيكل بدون معدل للقراءة فقط ؛ Y
ليس حقلاً.
تعمل نفس القواعد إذا كانت x هي معلمة ذات مُعدِّل في ، أو متغير محلي مع مُعدِّل مرجع للقراءة فقط ، أو نتيجة استدعاء أسلوب يُرجع قيمة من خلال مرجع للقراءة فقط.
إليك بعض القواعد التي يجب وضعها في الاعتبار. والأهم من ذلك ، أن الكود الذي يعتمد على هذه القواعد هش للغاية (أي أن التغييرات التي يتم إجراؤها على الكود تؤدي على الفور إلى تغييرات كبيرة في أجزاء أخرى من الكود أو الوثائق - الترجمة تقريبًا). كم عدد الأشخاص الذين سيلاحظون أن استبدال
public readonly int X
؛ على
public int X { get; }
public int X { get; }
في بنية مستخدمة بشكل متكرر دون تعديل للقراءة فقط يؤثر بشكل كبير على الأداء؟ أو ما مدى سهولة رؤية أن تمرير المعلمة باستخدام المُعدِّل بدلاً من التمرير حسب القيمة يمكن أن يؤدي إلى انخفاض الأداء؟ هذا ممكن حقًا عند استخدام خاصية معلمة في حلقة ، عندما يتم إنشاء نسخة واقية في كل تكرار.
هذه الخصائص للهياكل تروق حرفيا لتطوير المحللين. وسمعت المكالمة. تلبية
ErrorProne.NET - مجموعة من المحللين الذين يعلمونك بإمكانية تغيير رمز البرنامج لتحسين تصميمه وأدائه عند العمل مع الهياكل.
تحليل الشفرة مع إخراج الرسالة "اجعل بنية X للقراءة فقط"
أفضل طريقة لتجنب الأخطاء الدقيقة والآثار السلبية للأداء عند استخدام الهياكل هي جعلها للقراءة فقط كلما أمكن ذلك. يعبر معدّل القراءة فقط في إعلان الهيكل بوضوح عن نية المطور (مع التأكيد على أن الهيكل غير قابل للتغيير) ويساعد المترجم على تجنب إنتاج نسخ الأمان في العديد من السياقات المذكورة أعلاه.

الإعلان عن بنية للقراءة فقط لا ينتهك تكامل التعليمات البرمجية. يمكنك تشغيل المثبت بأمان (عملية إصلاح الرمز) في وضع الدُفعة وإعلان جميع هياكل حل البرنامج بالكامل للقراءة فقط.
الود لمعدل التعديل للقراءة فقط
تتمثل الخطوة التالية في تقييم أمان استخدام الميزات الجديدة (في المُعدِّل ، ومتغيرات القراءة المحلية ، والمتغيرات المرجعية ، وما إلى ذلك). هذا يعني أن المترجم لن ينشئ نسخًا واقية مخفية يمكن أن تقلل من الأداء.
يمكن اعتبار ثلاثة أنواع من الأنواع:
- مرجع الهياكل الصديقة للقراءة التي لا يؤدي استخدامها أبدًا إلى إنشاء نسخ واقية ؛
- الهياكل غير الصديقة للتحكيم للقراءة فقط ، والتي يؤدي استخدامها في سياق القراءة فقط دائمًا إلى إنشاء نسخ واقية ؛
- الهياكل المحايدة - الهياكل التي قد يؤدي استخدامها إلى نسخ واقية اعتمادًا على العضو المستخدم في سياق القراءة فقط.
تتضمن الفئة الأولى الهياكل للقراءة فقط وهياكل POCO. لن ينشئ المحول البرمجي نسخة واقية أبدًا إذا كانت البنية للقراءة فقط. من الآمن أيضًا استخدام هياكل POCO في سياق القراءة فقط: يعتبر الوصول إلى الحقول آمنًا ، ولا يتم إنشاء نسخ واقية.
الفئة الثانية هي هياكل بدون معدّل للقراءة فقط ولا تحتوي على حقول مفتوحة. في هذه الحالة ، فإن أي وصول إلى العضو العام في سياق القراءة فقط سيؤدي إلى إنشاء نسخة واقية.
الفئة الأخيرة هي الهياكل ذات المجالات العامة أو الداخلية والممتلكات أو الأساليب العامة أو الداخلية. في هذه الحالة ، يقوم المترجم بإنشاء نسخ واقية اعتمادًا على العضو المستخدم.
يساعد هذا الفصل على توليد تحذيرات على الفور إذا تم تمرير البنية "غير الصديقة" مع المُعدِّل في المُخزن في المُتغيّر المحلي المُرجَح للقراءة فقط ، إلخ.

لا يعرض المحلل تحذيرًا إذا تم استخدام البنية "غير الصديقة" كحقل للقراءة فقط ، حيث لا يوجد بديل في هذه الحالة. تم تصميم معدِّلات الإدخال والإرجاع للقراءة فقط لتحسينها على وجه التحديد لتجنب إنشاء نسخ مكررة. إذا كانت البنية "غير ودية" فيما يتعلق بهذه المعدلات ، فلديك خيارات أخرى: تمرير وسيطة حسب القيمة أو حفظ نسخة في متغير محلي. في هذا الصدد ، تتصرف الحقول للقراءة فقط بشكل مختلف: إذا كنت ترغب في جعل النوع غير قابل للتغيير ، يجب عليك استخدام هذه الحقول. تذكر: يجب أن يكون الرمز واضحًا وأنيقًا وسريعًا بشكل ثانوي.
تحليل نسخة مخفية الوجهة
يقوم المترجم بالعديد من الإجراءات المخفية عن المستخدم. كما هو موضح في منشور سابق ، من الصعب جدًا معرفة متى يتم إنشاء نسخة واقية.
يكتشف المحلل النسخ المخفية التالية:
- نسخة مخفية من حقل للقراءة فقط.
- نسخة مخفية من.
- نسخة مخفية من المتغير المحلي للقراءة فقط.
- نسخة مخفية الوجهة المرجع للقراءة فقط.
- نسخة مخفية عند استدعاء طريقة تمديد تأخذ معلمة مع هذا المعدل حسب القيمة لمثيل البنية.
public struct NonReadOnlyStruct { public readonly long PublicField; public long PublicProperty { get; } public void PublicMethod() { } private static readonly NonReadOnlyStruct _ros; public static void Samples(in NonReadOnlyStruct nrs) {
يرجى ملاحظة أن المحللون يعرضون رسائل التشخيص فقط إذا كان حجم الهيكل ≥16 بايت.
استخدام المحلل في المشاريع الحقيقية
إن نقل الهياكل الكبيرة حسب القيمة ، ونتيجة لذلك ، فإن إنشاء نسخ واقية من قبل المترجم يؤثر بشكل كبير على الأداء. تظهر نتائج اختبارات الأداء على الأقل هذا. ولكن كيف ستؤثر هذه الظواهر على التطبيقات الحقيقية من حيث الوقت حتى النهاية؟
لاختبار المحللون باستخدام رمز حقيقي ، استخدمتها في مشروعين: مشروع Roslyn والمشروع الداخلي الذي أعمل عليه حاليًا في Microsoft (المشروع هو تطبيق كمبيوتر مستقل بمتطلبات أداء صارمة) ؛ دعنا نسميها "المشروع د" للتوضيح.
ها هي النتائج:
- عادةً ما تحتوي المشاريع التي تتطلب متطلبات عالية الأداء على العديد من الهياكل ، ويمكن قراءة معظمها فقط. على سبيل المثال ، في مشروع Roslyn ، وجد المحلل حوالي 400 بنية يمكن قراءتها فقط ، وفي مشروع D ، حوالي 300.
- في المشروعات ذات متطلبات الأداء العالي ، يجب إنشاء النسخ المخفية فقط في حالات استثنائية. لم أجد سوى عدد قليل من هذه الحالات في مشروع روسلين ، لأن معظم الهياكل بها حقول عامة بدلاً من الممتلكات العامة. هذا يتجنب إنشاء نسخ واقية في الحالات التي يتم فيها تخزين الهياكل في حقول للقراءة فقط. كان هناك عدد أكبر من النسخ العمياء في المشروع D ، لأن نصفها على الأقل يمتلك خصائص فقط (وصول للقراءة فقط).
- من المرجح أن يكون لنقل الهياكل الكبيرة إلى حد ما باستخدام المعدل الداخلي تأثير قليل جدًا (غير محسوس تقريبًا) على مدار البرنامج.
لقد غيرت جميع الهياكل 300 في المشروع D ، مما جعلها للقراءة فقط ، ثم قمت بتصحيح مئات الحالات من استخدامها ، مشيرة إلى أنها تمرر مع المعدل في. ثم قمت بقياس وقت العبور من طرف إلى طرف لسيناريوهات الأداء المختلفة. كانت الاختلافات غير ذات دلالة إحصائية.
هل هذا يعني أن الميزات المذكورة أعلاه عديمة الفائدة؟ لا على الإطلاق.
يشير العمل في مشروع ذي متطلبات عالية الأداء (على سبيل المثال ، في Roslyn أو "Project D") إلى أن عددًا كبيرًا من الأشخاص يقضون الكثير من الوقت في أنواع مختلفة من التحسين. في الواقع ، في بعض الحالات ، تم تمرير الهياكل في الكود الخاص بنا مع مُعدِّل المرجع ، وتم الإعلان عن بعض الحقول بدون المُعدِّل للقراءة فقط لاستبعاد إنشاء النسخ الواقية. قد يعني نقص نمو الإنتاجية أثناء نقل الهياكل باستخدام المُعدِّل أن الشفرة تم تحسينها بشكل جيد ولا توجد نسخ مفرط من الهياكل على المسارات الحرجة لمرورها.
ماذا علي أن أفعل بهذه الميزات؟
أعتقد أن مسألة استخدام مُعدِّل للقراءة فقط للهياكل لا يتطلب الكثير من التفكير. إذا كان الهيكل غير قابل للتغيير ، فإن المعدل المعد للقراءة فقط يجبر المترجم بشكل صريح على قرار التصميم. ونقص النسخ الواقية لهذه الهياكل هو مجرد مكافأة.
اليوم توصياتي هي على النحو التالي: إذا كان يمكن قراءة الهيكل فقط ، فبكل الوسائل تجعله بهذه الطريقة.
استخدام الخيارات الأخرى التي تم النظر فيها له فروق دقيقة.
التحسين المسبق مقابل التشاؤم المسبق؟
يقدم Herb Sutter مفهوم "التشاؤم الأولي" في كتابه المذهل ،
معايير الترميز C ++: 101 قاعدة وتوصيات وأفضل الممارسات .
"Citris paribus ، تعقيد الشفرة وإمكانية قراءتها ، يجب أن تستنزف بعض أنماط التصميم الفعالة وعبارات التشفير بشكل طبيعي من أطراف أصابعك. مثل هذا الرمز ليس أكثر صعوبة في الكتابة من بدائله المتشائمة. أنت لا تقوم بالتحسين الأولي ، لكن تجنب التشاؤم الطوعي. "
من وجهة نظري ، فإن المعلمة مع المعدِّل هي الحالة فقط. إذا كنت تعلم أن الهيكل كبير نسبيًا (40 بايت أو أكثر) ، فيمكنك دائمًا تمريره باستخدام المعدّل في. تكلفة استخدام المُعدِّل في منخفضة نسبيًا ، لأنك لست بحاجة إلى ضبط المكالمات ، وقد تكون الفوائد حقيقية.
في المقابل ، بالنسبة للمتغيرات المحلية وقيم الإرجاع مع معدّل المرجع للقراءة فقط ، ليس هذا هو الحال. أود أن أقول أنه يجب استخدام هذه الميزات عند ترميز المكتبات ، ومن الأفضل رفضها في رمز التطبيق (فقط إذا لم يكشف توصيف الشفرة أن عملية النسخ مشكلة حقًا). يتطلب استخدام هذه الميزات جهدًا إضافيًا ، ويصبح من الصعب على قارئ الشفرة فهمها.
الخلاصة
- استخدم مُعدِّل للقراءة فقط للهياكل قدر الإمكان.
- ضع في اعتبارك استخدام المعدّل في الهياكل الكبيرة.
- ضع في اعتبارك استخدام المتغيرات المحلية وقيم الإرجاع مع معدِّل المرجع للقراءة فقط لترميز المكتبات أو في الحالات التي تشير فيها نتائج توصيف التعليمات البرمجية إلى أن ذلك يمكن أن يكون مفيدًا.
- استخدم ErrorProne.NET للكشف عن مشاكل التعليمات البرمجية ومشاركة النتائج.