منذ وقت ليس ببعيد ، ظهر مقال ممتاز حول Habré Optimization of garbage collection في خدمة .NET محملة بدرجة عالية . هذه المقالة مثيرة للاهتمام للغاية لأن المؤلفين ، المسلحين بالنظرية ، فعلوا المستحيل في وقت سابق: لقد قاموا بتحسين تطبيقهم باستخدام معرفة GC. وإذا لم يكن لدينا في وقت سابق أي فكرة عن كيفية عمل هذا GC ، يتم تقديمه إلينا الآن على طبق فضي من خلال جهود Konrad Cocos في كتابه Pro .NET Memory Management . ما هي الاستنتاجات التي استخلصتها بنفسي؟ دعنا نضع قائمة بالمناطق التي نواجه مشاكل ونفكر في كيفية حلها.
في ورشة عمل CLRium # 5 الأخيرة: جامع القمامة ، تحدثنا عن GC طوال اليوم. ومع ذلك ، قررت نشر تقرير واحد مع فك تشفير النص. هذا هو الحديث عن الاستنتاجات المتعلقة تحسين التطبيق.
تقليل الاتصال عبر الجيل
المشكلة
لتحسين سرعة جمع القمامة ، يقوم GC بجمع الجيل الأصغر كلما كان ذلك ممكنًا. ولكن للقيام بذلك ، يحتاج أيضًا إلى معلومات حول الروابط من الأجيال الأكبر سناً (وهي في هذه الحالة بمثابة جذر إضافي): من جدول البطاقات.
في الوقت نفسه ، يفرض عليك رابط واحد من الأقدم إلى الجيل الأصغر سناً أن تغطي المنطقة بطاولة بطاقة:
- 4 بايت تداخل 4 كيلو بايت أو كحد أقصى. 320 كائنات - للعمارة x86
- 8 بايت تداخل 8 كيلو بايت أو كحد أقصى. 320 كائنات - للعمارة إلى x64
أي سيضطر GC ، عند التحقق من جدول البطاقة ، وتلبية قيمة غير صفرية فيه ، إلى التحقق من 320 كائنًا كحد أقصى لوجود روابط صادرة في جيلنا.
وبالتالي فإن الروابط المتفرقة في جيل الشباب ستجعل GC أكثر استهلاكا للوقت
قرار
- تحديد موقع الكائنات مع الاتصالات في جيل الشباب - قريب.
- إذا كان من المفترض حركة مرور الكائنات ذات الجيل صفر ، استخدم السحب. أي قم بإنشاء مجموعة من الكائنات (لن يكون هناك كائنات جديدة: لن تكون هناك كائنات من الجيل صفر). وعلاوة على ذلك ، من خلال "الاحماء" لحمام السباحة مع اثنين من جي بي سي متتالية بحيث مضمونة الفشل في الجيل الثاني ، وبالتالي يمكنك تجنب الروابط إلى جيل الشباب ولديك أصفار في طاولة البطاقة ؛
- تجنب الروابط إلى جيل الشباب ؛
تجنب الاتصال القوي
المشكلة
كما يلي من خوارزميات مرحلة ضغط الكائنات في SOH:
- لضغط الكومة ، تحتاج إلى الالتفاف على الشجرة والتحقق من جميع الروابط ، وتصحيحها للحصول على قيم جديدة
- علاوة على ذلك ، تؤثر الروابط من جدول البطاقات على مجموعات كاملة من الكائنات
لذلك ، يمكن أن تؤدي التوصيلية العامة القوية للكائنات إلى هبوط خلال GC.
قرار
- لديك كائنات مرتبطة بقوة في مكان قريب ، في جيل واحد
- تجنب الارتباطات غير الضرورية بشكل عام (على سبيل المثال ، بدلاً من تكرار هذا الارتباط> ارتباطات المقبض ، استخدم المؤشر الموجود بالفعل-> الخدمة->)
- تجنب رمز مع اتصال خفية. على سبيل المثال ، الإغلاق
مراقبة استخدام القطاع
المشكلة
أثناء العمل المكثف ، قد ينشأ موقف ما عندما يؤدي تخصيص كائنات جديدة إلى تأخير: تخصيص شرائح جديدة أسفل الكومة وتراجعها عند تنظيف القمامة
قرار
- استخدام PerfMon / Sysinternal Utilities للتحكم في نقاط اختيار القطاعات الجديدة وإيقاف تشغيلها وإصدارها
- إذا كنا نتحدث عن LOH ، وهي حركة مرور عازلة كثيفة ، فاستخدم ArrayPool
- إذا كنا نتحدث عن SOH ، فتأكد من إبراز الكائنات التي لها نفس العمر في مكان قريب ، مع توفير Sweep بدلاً من Collect
- SOH: استخدام تجمعات الكائنات
لا تخصص ذاكرة في مقاطع محملة من الكود
المشكلة
يخصص القسم المحمّل من الكود الذاكرة:
- نتيجةً لذلك ، يحدد GC إطار تخصيص ليس 1 كيلو بايت ، ولكن 8 كيلو بايت.
- في حالة نفاد مساحة النافذة ، يؤدي ذلك إلى توسع في المنطقة المغلقة وتوسيع نطاقها
- يؤدي الدفق الكثيف من الكائنات الجديدة إلى جعل الكائنات قصيرة العمر من مؤشرات الترابط الأخرى تنتقل سريعًا إلى الجيل الأقدم مع ظروف جمع البيانات المهملة الأسوأ
- مما سيزيد من وقت جمع القمامة
- مما سيؤدي إلى إيقاف العالم لفترة أطول حتى في الوضع المتزامن
قرار
- الحظر التام على استخدام عمليات الإغلاق في الأقسام الهامة من التعليمات البرمجية
- الحظر الكامل للملاكمة على الأقسام المهمة من الكود (يمكنك استخدام المحاكاة من خلال السحب إذا لزم الأمر)
- عند الضرورة لإنشاء كائن مؤقت لتخزين البيانات ، استخدم الهياكل. الأفضل هو المرجع الهيكل. عندما يكون عدد الحقول أكثر من 2 ، أرسل بواسطة المرجع
تجنب تخصيصات الذاكرة غير الضرورية في LOH
المشكلة
يؤدي وضع المصفوفات في LOH إلى تجزئة أو ترجيح إجراء GC
قرار
- استخدم تقسيم الصفائف إلى صفائف فرعية وفصلًا يحتوي على منطق للعمل مع هذه الصفائف (أي بدلاً من List <T> ، حيث يتم تخزين الصفيف الضخم ، وقائمة MyList مع صفيف [] ،] تقسم الصفيف أقصر قليلاً)
- سوف المصفوفات الذهاب إلى SOH
- بعد بضع مجموعات من القمامة ، سوف يستلقي بجوار الكائنات الحية دائمًا ويتوقف عن التأثير على مجموعة القمامة
- تحكم في استخدام المصفوفات المزدوجة بطول أكثر من 1000 عنصر.
حيثما كان ذلك ممكنًا ومبررًا ، استخدم ترابط الصفحات
المشكلة
هناك عدد من الكائنات فائقة القصر أو الكائنات التي تعيش داخل استدعاء الأسلوب (بما في ذلك المكالمات الداخلية). أنها تخلق حركة المرور الكائن
قرار
- باستخدام تخصيص الذاكرة على المكدس حيثما أمكن:
- لا يتم تحميل حفنة
- لا يتم تحميل GC
- الافراج عن الذاكرة - لحظة
- استخدم
Span T x = stackalloc T[];
بدلا من new T[]
حيثما كان ذلك ممكنا - استخدام
Span/Memory
حيثما كان ذلك ممكنا - تحويل الخوارزميات إلى أنواع
ref stack
(StackList: struct، ValueStringBuilder )
الأشياء الحرة في أقرب وقت ممكن
المشكلة
على أنها قصيرة العمر ، تقع الكائنات في gen1 ، وأحيانًا في gen2.
هذه النتائج في GC أثقل الذي يستمر لفترة أطول
قرار
- يجب عليك تحرير مرجع الكائن في أقرب وقت ممكن
- إذا كانت الخوارزمية المطولة تحتوي على تعليمات برمجية تعمل مع أي كائنات ، فسيتم تباعدها برمز. لكن التي يمكن تجميعها في مكان واحد ، من الضروري تجميعها ، وبالتالي السماح بجمعها في وقت مبكر.
- على سبيل المثال ، في السطر 10 ، تم إخراج المجموعة ، وفي السطر 120 ، تمت تصفيتها.
لا حاجة للاتصال بـ GC.Collect ()
المشكلة
يبدو غالبًا أنه إذا اتصلت بـ GC.Collect () ، فسيؤدي ذلك إلى إصلاح الموقف
قرار
- من الأصح بكثير معرفة خوارزميات تشغيل GC ، والنظر إلى التطبيق تحت ETW وأدوات التشخيص الأخرى (JetBrains dotMemory ، ...)
- تحسين المناطق الأكثر إشكالية
تجنب تثبيت
المشكلة
تعلق يطرح عددا من المشاكل:
- يعقد جمع القمامة
- ينشئ مسافات ذاكرة مجانية (عناصر قائمة خالية من العقد ، جدول طوب ، مجموعات)
- قد تترك بعض الكائنات في جيل الشباب ، في حين تشكل روابط من جدول البطاقات
قرار
إذا لم يكن هناك مخرج آخر ، فاستخدم ثابت () {}. طريقة الالتزام هذه لا تقدم التزامًا حقيقيًا: فهي تحدث فقط عندما يعمل GC داخل المشابك المجعدة.
تجنب الانتهاء
المشكلة
لا يُسمى اللمسات الأخيرة بشكل نهائي:
- يؤدي التخلص غير المدعوم () إلى الانتهاء مع كل الروابط الصادرة من الكائن
- يتم تأخير الكائنات التابعة لفترة أطول من المخطط لها
- الشيخوخة ، والانتقال إلى الأجيال الأكبر سنا
- إذا كانت تحتوي في الوقت نفسه على روابط لأصغر سنا ، فإنها تنشئ روابط من جدول البطاقات
- تعقيد تجميع الأجيال القديمة وتفتيتهم وتؤدي إلى الضغط بدلاً من المسح
قرار
استدعاء بلطف التخلص ()
تجنب الكثير من المواضيع
المشكلة
مع عدد كبير من مؤشرات الترابط ، ينمو سياق التخصيص ، مثل تم تخصيصها لكل مؤشر ترابط:
- نتيجة لذلك ، يأتي GC.Collect بشكل أسرع.
- نظرًا لنقص المساحة في المقطع المؤقت ، ستتبع عملية التجميع Collective Sweep
قرار
- التحكم في عدد المواضيع من خلال عدد النوى
تجنب حركة الكائنات ذات الأحجام المختلفة
المشكلة
عند تجزئة الكائنات ذات الأحجام والأعمار المختلفة ، يحدث التجزئة:
- زيادة نسبة التفتت
- مجموعة التشغيل باستخدام مرحلة تغيير العنوان في كل الكائنات المرجعية
قرار
إذا كان من المفترض حركة مرور الكائنات:
- تحقق من وجود حقول إضافية ، تقريب الحجم
- تحقق من عدم وجود معالجة سلسلة: حيثما أمكن ، استبدل ReadOnlySpan / ReadOnlyMemory
- الافراج عن الرابط في أقرب وقت ممكن
- الاستفادة من السحب
- قم بتسخين مخابئ وأحواض دافئة مع GC مزدوج لضغط الأشياء. وبالتالي ، يمكنك تجنب مشاكل مع جدول البطاقة.