لقد اكتشفت بالصدفة تعريف "الخنزير في بايثون (ملاحظة: إنه يشبه إلى حد ما الثعبان الجشع وغير الكافي الذي يبتلع الفيل)" باللغة الصينية عندما كنت أنظر إلى مسرد إدارة الذاكرة، لذلك توصلت إلى هذه المقالة. ظاهريًا، يشير هذا المصطلح إلى قيام GC بالترويج المستمر للأشياء الكبيرة من جيل إلى آخر. القيام بذلك يشبه ثعبانًا يبتلع فريسته كاملة، بحيث لا يستطيع التحرك أثناء هضمه.
طوال الـ 24 ساعة التالية، كان ذهني مليئًا بصور هذا الثعبان الخانق الذي لم أتمكن من التخلص منه. وكما يقول الأطباء النفسيون، فإن أفضل طريقة للتخلص من الخوف هي التحدث عنه. ومن هنا هذه المقالة. لكن القصة التالية التي نريد التحدث عنها ليست بايثون، ولكن ضبط GC. اقسم بالله.
يعلم الجميع أن توقف GC مؤقتًا يمكن أن يتسبب بسهولة في اختناقات في الأداء. تأتي JVMs الحديثة مزودة بأدوات تجميع البيانات المهملة المتقدمة عند إصدارها، ولكن من تجربتي، من الصعب للغاية العثور على التكوين الأمثل لتطبيق معين. ربما لا يزال هناك بصيص من الأمل في الضبط اليدوي، لكن عليك أن تفهم الآليات الدقيقة لخوارزمية GC. في هذا الصدد، ستكون هذه المقالة مفيدة لك أدناه وسأستخدم مثالاً لشرح كيفية تأثير تغيير بسيط في تكوين JVM على إنتاجية تطبيقك.
مثال
كان التطبيق الذي استخدمناه لتوضيح تأثير GC على الإنتاجية عبارة عن برنامج بسيط. ويحتوي على موضوعين:
PigEater سوف يقلد عملية أكل الثعبان العملاق لخنزير كبير سمين. يقوم الكود بذلك عن طريق إضافة 32 ميجابايت بايت إلى java.util.List والنوم لمدة 100 مللي ثانية بعد كل ابتلاع.
PigDgester يحاكي عملية الهضم غير المتزامن. الكود الذي ينفذ عملية الهضم يقوم ببساطة بتعيين قائمة الخنازير فارغة. نظرًا لأن هذه عملية متعبة، فإن هذا الخيط سوف ينام لمدة 2000 مللي ثانية في كل مرة بعد مسح المرجع.
سيعمل كلا الخيطين في حلقة زمنية، حيث يأكلان ويهضمان حتى يمتلئ الثعبان. وهذا يتطلب تناول ما يقرب من 5000 خنزير.
انسخ رمز الكود كما يلي:
package eu.plumbr.demo;
الطبقة العامة PigInThePython {
قائمة الخنازير الثابتة المتغيرة = new ArrayList();
الخنازير المتطايرة الثابتةEaten = 0;
العدد النهائي الثابت ENOUGH_PIGS = 5000؛
public static void main(String[] args) يلقي InterruptedException {
جديد PigEater().start();
جديد PigDigester().start();
}
فئة ثابتة PigEater تمتد الموضوع {
@تجاوز
تشغيل الفراغ العام () {
بينما (صحيح) {
pigs.add(new byte[32 * 1024 * 1024]); //32 ميجابايت لكل خنزير
إذا عاد (pigsEaten > ENOUGH_PIGS)؛
takeANap(100);
}
}
}
فئة ثابتة PigDigester تمتد الموضوع {
@تجاوز
تشغيل الفراغ العام () {
بداية طويلة = System.currentTimeMillis();
بينما (صحيح) {
takeANap(2000);
pigsEaten+=pigs.size();
الخنازير = قائمة ArrayList الجديدة () ؛
إذا (pigsEaten > ENOUGH_PIGS) {
System.out.format("تم هضم %d من الخنازير في %d مللي ثانية.%n",pigsEaten, System.currentTimeMillis()-start);
يعود؛
}
}
}
}
ثابت باطل takeANap (int مللي ثانية) {
يحاول {
Thread.sleep(ms);
} قبض (الاستثناء ه) {
printStackTrace();
}
}
}
الآن نحدد إنتاجية هذا النظام بأنها "عدد الخنازير التي يمكن هضمها في الثانية". بالنظر إلى أنه يتم حشو خنزير في هذا الثعبان كل 100 مللي ثانية، يمكننا أن نرى أن الحد الأقصى للإنتاجية النظرية لهذا النظام يمكن أن يصل إلى 10 خنازير / ثانية.
مثال تكوين GC
دعونا نلقي نظرة على أداء استخدام نظامين تكوين مختلفين. بغض النظر عن التكوين، يعمل التطبيق على جهاز Mac ثنائي النواة (OS X10.9.3) مزود بذاكرة وصول عشوائي (RAM) سعة 8 جيجابايت.
التكوين الأول:
كومة 1.4 جيجا (-Xms4g -Xmx4g)
2. استخدم CMS لتنظيف الجيل القديم (-XX:+UseConcMarkSweepGC) واستخدم المجمع المتوازي لتنظيف الجيل الجديد (-XX:+UseParNewGC)
3. تخصيص 12.5% من الكومة (-Xmn512m) للجيل الجديد، وتحديد حجم منطقة Eden ومنطقة Survivor ليكونا متساويين.
التكوين الثاني مختلف قليلاً:
كومة 1.2 جيجا (-Xms2g -Xms2g)
2. يستخدم كل من الجيل الجديد والجيل القديم Parellel GC (-XX:+UseParallelGC)
3. تخصيص 75% من الكومة للجيل الجديد (-Xmn 1536m)
4. حان الوقت الآن للمراهنة، ما هو التكوين الذي سيؤدي بشكل أفضل (كم عدد الخنازير التي يمكن أن تؤكل في الثانية، تذكر)؟ أولئك الذين وضعوا رقائقهم في التكوين الأول سيصابون بخيبة أمل. والنتيجة هي العكس تماما:
1. التكوين الأول (الكومة الكبيرة، الجيل القديم الكبير، CMS GC) يمكنه تناول 8.2 خنازير في الثانية
2. التكوين الثاني (كومة صغيرة، جيل جديد كبير، Parellel GC) يمكنه تناول 9.2 خنزير في الثانية
والآن دعونا ننظر إلى هذه النتيجة بموضوعية. الموارد المخصصة أقل مرتين ولكن يتم زيادة الإنتاجية بنسبة 12%. وهذا يتعارض مع المنطق السليم، لذلك من الضروري إجراء مزيد من التحليل لما يجري.
تحليل نتائج GC
السبب في الواقع ليس معقدًا، يمكنك العثور على الإجابة من خلال إلقاء نظرة فاحصة على ما يفعله GC عند إجراء الاختبار. هذا هو المكان الذي تختار فيه الأداة التي تريد استخدامها. بمساعدة jstat، اكتشفت السر وراء ذلك، الأمر على الأرجح كالتالي:
انسخ رمز الكود كما يلي:
jstat -gc -t -h20 PID 1s
عند تحليل البيانات، لاحظت أن التكوين 1 مر بـ 1129 دورة GC (YGCT_FGCT)، واستغرق إجمالي 63.723 ثانية:
انسخ رمز الكود كما يلي:
الطابع الزمني S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
594.0 174720.0 174720.0 163844.1 0.0 174848.0 131074.1 3670016.0 2621693.5 21248.0 2580.9 1006 63.182 116 0.236 63.419
595.0 174720.0 174720.0 163842.1 0.0 174848.0 65538.0 3670016.0 3047677.9 21248.0 2580.9 1008 63.310 117 0.236 63.546
596.1 174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0 491772.9 21248.0 2580.9 1010 63.354 118 0.240 63.595
597.0 174720.0 174720.0 0.0 163840.1 174848.0 131074.1 3670016.0 688380.1 21248.0 2580.9 1011 63.482 118 0.240 63.723
توقف التكوين الثاني مؤقتًا إجمالي 168 مرة (YGCT+FGCT) واستغرق 11.409 ثانية فقط.
انسخ رمز الكود كما يلي:
الطابع الزمني S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
539.3 164352.0 164352.0 0.0 0.0 1211904.0 98306.0 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
540.3 164352.0 164352.0 0.0 0.0 1211904.0 425986.2 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
541.4 164352.0 164352.0 0.0 0.0 1211904.0 720900.4 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
542.3 164352.0 164352.0 0.0 0.0 1211904.0 1015812.6 524288.0 164352.2 21504.0 2579.2 27 2.969 141 8.441 11.409
وبالنظر إلى أن عبء العمل في كلتا الحالتين متساوي، لذلك - في تجربة أكل الخنازير هذه، عندما لا يجد GC كائنات طويلة العمر، يمكنه تنظيف الكائنات غير المرغوب فيها بشكل أسرع. مع التكوين الأول، سيكون تكرار تشغيل GC حوالي 6 إلى 7 مرات، وسيكون إجمالي وقت الإيقاف المؤقت من 5 إلى 6 مرات.
رواية هذه القصة تخدم غرضين. أولًا والأهم من ذلك، أردت إخراج هذا الثعبان المتشنج من ذهني. المكسب الآخر الأكثر وضوحًا هو أن ضبط GC يعد تجربة ماهرة جدًا، ويتطلب منك أن يكون لديك فهم شامل للمفاهيم الأساسية. على الرغم من أن التطبيق المستخدم في هذه المقالة هو مجرد تطبيق شائع جدًا، إلا أن النتائج المختلفة للاختيار سيكون لها أيضًا تأثير كبير على الإنتاجية وتخطيط السعة. وفي تطبيقات الحياة الواقعية، سيكون الفرق هنا أكبر. لذا فالأمر متروك لك، يمكنك إتقان هذه المفاهيم، أو يمكنك فقط التركيز على عملك اليومي والسماح لـ Plumbr باكتشاف تكوين GC الأكثر ملاءمة لاحتياجاتك.