تحليل تسرب الذاكرة في Devel::MAT - Perl

March 18, 2024

نظرة على حالة نمو الذاكرة البطيء وكيف يمكن للميزات التي أضيفت مؤخرًا في إصدار Devel::MAT 0.38 أن تساعد في الوصول إلى جوهر المشكلة.

نمو بطيء على مدى الوقت

عند وجود عملية تستهلك كمية صغيرة من الذاكرة مع مرور الوقت، قد يكون من الصعب أحيانًا البحث عن سبب ذلك عبر ملف تفريغ كومة واحد يحتوي على لحظة مجمدة وحيدة من الزمن. حجم العملية الكلي قد يكون كبيرًا مقارنةً بالكمية الصغيرة التي تنمو بها. ما لم تكن العملية قديمة جدًا وبالتالي تراكمت لديها كمية كبيرة من النمو، فلن يكون من السهل تحديد مكان المشكلة.

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

طبيعة SV المتسرب هي أنها تُخصص في وقت ما، ومن ثم لا يُلمس بعدها - تبقى موجودة دائمًا بنفس النوع على نفس العنوان. يوجد سكريبت جديد تمت إضافته في توزيع Devel-MAT يبحث عن SVs التي تطابق هذا النمط. لتشغيل هذا، سنحتاج إلى سلسلة من ملفات تفريغ الكومة المأخوذة من العملية.

تفريغات كومة آلية

الحالة التي ننظر إليها اليوم تتضمن تسربًا مشتبهًا به في bom-feed-distributor، لأن العملية تنمو في استخدام الذاكرة مع الوقت. تمت ملاحظة هذا من خلال فحص دوري لحقل VSZ في إخراج أداة ps.

للحصول على بعض تفريغات الكومة من هذا البرنامج، نبدأ بتحرير السكريبت الرئيسي له، مضيفين سطرًا لتحميل Devel::MAT::Dumper مُعد لكتابة ملف تفريغ كومة عند استلام إشارة SIGQUIT. معالج الإشارة هذا مخصص لهذا الهدف، لأنه لا يوقف العملية بعد الانتهاء، بل يستأنف العمل بشكل طبيعي بعد كتابة الملف.

use Devel::MAT::Dumper qw( -dump_at_SIGQUIT -file /tmp/bom-feed-distributor-NNN.pmat );

كلما أرسلنا إشارة SIGQUIT سنحصل على ملف تفريغ كومة برقم تسلسلي جديد في /tmp. يمكننا أتمتة هذا ليعمل بانتظام، وبالتالي تركه يعمل طوال الليل لجمع بعض الملفات للتحليل لاحقًا:

$ PID=$(pgrep -f bom-feed-distributor)

$ while sleep $PID; do kill -QUIT 30829; done

التحليل التفريقي

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

$ pmat-leakreport -1 bom-feed-distributor-2*.pmat > leakreport.txt

سوف نأخذ سلسلة كاملة من عشرة ملفات مرقمة من 20 إلى 29، حيث ستكون بعيدة بما فيه الكفاية عن بدء العملية لتكون مستقرة، مما يقلل من الإيجابيات الكاذبة. نوجه الناتج إلى ملف في حال كان تقرير التسرب طويلًا جدًا - سنحتاج إلى الرجوع إليه مرارًا وتكرارًا. توليد التقرير يتطلب حوسبة مكثفة، لذا من المفيد الاحتفاظ به على القرص للرجوع إليه.

من خلال النظر إلى بداية هذا الملف، يمكننا رؤية عدد من SVs التي قد تكون مرشحة مثيرة للتحقيق:

$ head -10 leakreport-2.txt
LEAK[1] SCALAR(UV,NV,PV) at 0x40ed0d0
LEAK[1] SCALAR(UV) at 0x5ed26d8
LEAK[1] REF() at 0x5f76ec8
LEAK[1] SCALAR(PV) at 0x5ecbf88
LEAK[1] SCALAR(UV) at 0x5e6e9d8
LEAK[1] REF() at 0x5fad540
LEAK[1] REF() at 0x5e59228
LEAK[1] REF() at 0x5fac498
LEAK[1] SCALAR(UV) at 0x5fd15d8
LEAK[1] SCALAR(UV) at 0x5f18998

لنبدأ باختيار إحدى هذه الروابط ونرى ما هي. بما أن هذه SVs بقيت دون تغيير إلى نهاية سلسلة ملفات .pmat، يمكننا تحميل أحدثها في بيئة pmat ومحاولة تحديد هويتها:

$ pmat bom-feed-distributor-29.pmat
ملف تفريغ ذاكرة Perl من perl 5.14.2
الكومة تحتوي على 266212 كائن
pmat> identify 0x5f76ec8
REF() في 0x5f76ec8 هو:
└─غير موجود

هذا مثير بعض الشيء بالفعل. كائن SV متسرب لا يستطيع identify تحديده. قد يكون السبب هو أنه لم يعد مشارًا إليه من أي مكان لكنه ما زال يحتوي على عداد مراجع غير صفري، أو لأنه مشار إليه بواسطة شيء لا يستطيع Devel::MAT::Dumper رؤيته.

كائنات SV غير قابلة للتحديد

لنحاول معرفة ما هو هذا المرجع، لنرى إن كان بإمكاننا معرفة المزيد عنه:

pmat> show 0x5f76ec8
REF() عند 0x5f76ec8 مع عداد مراجع يساوي 1
 الحجم 24 بايت
 RV=HASH(14)=Date::Utility عند 0x5f60f88

إنه كائن من نوع Date::Utility. لنلقي نظرة على SV آخر ونرى ما إذا كنا نستطيع إيجاد نمط:

pmat> identify 0x5ed26d8
SCALAR(UV) عند 0x5ed26d8 هو:
└─قيمة {month} من HASH(14)=Date::Utility عند 0x5f02008، وهو:
 └─المرجع للـ REF() عند 0x60c9730، وهو:
   └─غير موجود

pmat> identify 0x40ed0d0
SCALAR(UV,NV,PV) عند 0x40ed0d0 هو:
└─قيمة {epoch} من HASH(14)=Date::Utility عند 0x5ef57b8، وهو:
 └─المرجع للـ REF() عند 0x5f7f910، وهو:
   └─غير موجود

بدأ نمط ما بالظهور. معظم هذه الكائنات المتسربة من نوع SV مرتبطة بكائنات Date::Utility. ربما يوجد عدد كبير منها في هذا الملف - يجب علينا عدهم جميعًا. ميزة جديدة في Devel::MAT 0.38 تسمح لنا بفعل ذلك، باستخدام الخيار --count مع أمر find. نطلب منها عد عدد SVs الموجودة ضمن هذا الحزمة:

pmat> find --count blessed Date::Utility
المجموع: 308 SVs

هذه كمية لا بأس بها من مثيلات كائن المساعدة التي تتواجد باستمرار في مثيل الخادم. يمكننا استخدام الملفات السابقة لاكتشاف التاريخ هنا؛ باستخدام خيار --quiet مع بيئة pmat لتقليل الضوضاء غير المتعلقة في الناتج:

$ for F in bom-feed-distributor-*.pmat; do echo -n "$F: "; pmat --quiet $F find --count blessed Date::Utility; done
bom-feed-distributor-00.pmat: المجموع: 22 SVs
bom-feed-distributor-01.pmat: المجموع: 45 SVs
bom-feed-distributor-02.pmat: المجموع: 301 SVs
bom-feed-distributor-03.pmat: المجموع: 301 SVs
bom-feed-distributor-04.pmat: المجموع: 301 SVs
bom-feed-distributor-05.pmat: المجموع: 301 SVs
bom-feed-distributor-06.pmat: المجموع: 301 SVs
bom-feed-distributor-07.pmat: المجموع: 301 SVs
...
bom-feed-distributor-29.pmat: المجموع: 308 SVs

يبدو أن هذه الكائنات تتراكم بشكل سريع نسبيًا خلال بدء تشغيل البرنامج في الساعات الأولى، وتبقى بعد ذلك مستقرة عند ما يزيد قليلاً عن 300.

دلائل في شفرة المصدر

عند هذه النقطة، أعترف أنني توقفت عن التفكير في ما قد يحدث، لذلك طلبت المساعدة من الآخرين لرؤية إن كان لديهم أفكار. لاحظ توم ملوزورث ما يلي في Date/Utility.pm السطر 50:

my $lru = tie %popular, 'Tie::Hash::LRU', 300;

هنا نرى ذاكرة مؤقتة (cache) تم تنفيذها باستخدام الحزمة Tie::Hash::LRU، والتي تحافظ على 300 كائن. يبدو أن هذا السبب المحتمل جدًا للمشكلة التي نراها هنا. علاوة على ذلك، تستخدم هذه الحزمة تنفيذًا يعتمد على XS لذا قد تحتوي على مراجع كائنات بريل مخزنة داخل شفرة C التي لا يستطيع Devel::MAT::Dumper رؤيتها. هذا يفسر سبب عدم قدرة identify على توفير مزيد من المعلومات عنها.

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

بالإضافة إلى ذلك، رأينا أن هناك حدودًا لرؤية Devel::MAT::Dumper. على وجه التحديد، أن أشياء مثل الوحدات المطبقة بـ XS يمكن أن تخل برؤية العلاقة بين SVs التي تعيش على الكومة، مما يجعلنا لا نرى بعض التفاصيل.

أخيرًا، من المفيد تكرار أنه أحيانًا عندما تقوم بتحليل مشكلة وتواجه صعوبة، فإن النقاش مع زملائك يمكن أن يكشف عن مؤشرات وأفكار جديدة تساعد في حل المسألة.

ملاحظة: تم نقل هذا المنشور من https://tech.binary.com/ (مدونتنا التقنية القديمة). مؤلف هذه المقالة هو https://metacpan.org/author/PEVANS

المحتويات