أثناء قياس الأداء الجديد لـ elektrad ، عانيت من أعراض تسرب الذاكرة.
سيرتفع استخدام الذاكرة بعد إنشاء مقابض جديدة واستدعاء kdbGet()
لكن لا ينخفض أبدًا بعد ksClose()
و kdbClose()
.
كان شكوكي الأول هو التخزين المؤقت mmap - والذي ربما يتسبب أيضًا في حدوث تسرب للذاكرة لأن الذاكرة mmaped لا يتم تحريرها أبدًا - ولكن تعطيل التخزين المؤقت عن طريق البناء باستخدام -DPLUGINS="ALL;-cache"
لم يحل مشكلتي.
اذهب> = 1.13
لقد أنشأت اختبارين في go-elektra repo. كلاهما ينشئ مجموعة مفاتيح اختبار مع 100000 مفتاح.
TestKeySetMemory
مقابض ومجموعات مفاتيح kdbGets
في حلقة وبعد الانتظار لمدة ثانية واحدة - يغلق على الفور مجموعة المفاتيح + المقبض مرة أخرى قبل البدء من جديد.TestKeySetMemoryWithDelayedClose
أيضًا مقابض ويملأ مجموعات المفاتيح بـ kdbGets
- لكن يؤخر إغلاق المقبض ومجموعة المفاتيح حتى بعد تحميل جميع مجموعات المفاتيح العشرين. هذا يحصر سلوك خادم الويب elektrad.ينتظر كلا الاختبارين 20 ثانية بعد الانتهاء للسماح للمختبِر برؤية استهلاك الذاكرة للاختبار عبر htop
أو أدوات مشابهة.
الاختبار الأول ، الذي يغلق المقبض على الفور ومجموعة المفاتيح ، يحتفظ بنفس بصمة الذاكرة على طول الاختبار.
الاختبار الثاني الذي يبدأ في إغلاق المقابض ومجموعات المفاتيح فقط بعد "تحميل" كل مجموعة مفاتيح لا يحرر أي ذاكرة. حتى بعد الإجبار على جمع القمامة والانتظار لمدة 20 ثانية.
في الوقت الحالي ، لا أعرف سبب اختلاف سلوك هذه الاختبارات.
يمكنك إجراء الاختبارات عن طريق استنساخ go-elektra
repo وتشغيل هذين الأمرين في الدليل الفرعي ./kdb
:
PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test -run "^(TestKeySetMemory)\$"
PKG_CONFIG_PATH=<PATH TO elektra.pc FILE> go test -run "^(TestKeySetMemoryWithDelayedClose)\$"
يتم تحرير الذاكرة بعد kdbClose
و ksClose
لا تتحرر الذاكرة
يبدو لي أنه من غير المحتمل أن شيئًا كهذا قد ينزلق من خلال اختبارات Valgrind و ASAN. لقد قمت بتشغيل معايير مماثلة في C ولم أتمكن من ملاحظة نفس الشيء. حتى مع mmap لا يمكنني ملاحظة أي شيء مشابه. أنا لا أستبعد ذلك بالرغم من ذلك.
هل أنت متأكد من أن الارتباطات لا تتسرب في مكان ما؟
نعم ، أنا متأكد - من الممكن فحص استخدام الذاكرة لتطبيق go أثناء وقت التشغيل ، ولا تحتوي هذه الإحصائيات على تخصيصات بواسطة رمز C وهي ليست قريبة من إجمالي استخدام الذاكرة للتطبيق / الاختبار مما يعني أن يتم تخصيص الجزء الأكبر من الذاكرة في C.
شكوكي هو أن المفاتيح لم يتم تحريرها لأن عداد المرجع هو != 0
على الرغم من أنه يجب أن يكون 0
.
هذا يعني أن الروابط تستدعي واجهة برمجة التطبيقات C بطريقة تؤدي إلى تشغيل هذا المذكر. لسوء الحظ لا أعرف اذهب ، لذلك لا أعرف لماذا يحدث هذا.
هل يمكنك استخراج مثال C صغير يؤدي إلى حدوث التسرب؟ أفعل شيئًا مشابهًا جدًا في benchmarks/kdb.c
ولا يوجد تسرب هناك ، لذلك يجب أن تفعل بالضبط ما يفعله المعيار الخاص بك لإطلاق التسرب.
شكرا على الإبلاغ القضية!
أتفق تمامًا مع mpranj هنا ، نحن بحاجة إلى تقليل المشكلة إلى الحد الأدنى ، مع وجود لغات متعددة من الصعب العثور على أي شيء.
هل يمكنك كتابة برنامج C يعيد إنتاج تسلسل المكالمات كما تم إصداره من روابط go؟ بمجرد أن نحصل على ذلك ، يمكننا تصغير برنامج C لحالة اختبار.
بالمناسبة. في محاولة لتشغيل الأمر أعلاه ، فشلت مبكرًا: # 3159
سأحاول إعادة إنتاج هذا في C حتى الغد
لقد نجحت في إعادة إنتاج هذه المشكلة هنا . تشغيل benchmarks/memoryleak.c
القياسي ومشاهدة ذاكرة ترتفع حين الحصول على keysets - وليس النزول عند تحرير.
لا تنس أن تضيف كمية ملحوظة من المفاتيح في قاعدة البيانات .. لقد اختبرتها باستخدام 100 ألف مفتاح.
شكرا لخلق المثال! سألقي نظرة.
للوهلة الأولى ، هناك تسرب مباشر واحد بالضبط ، تحتاج إلى تحرير مفتاح الوالدين:
https://github.com/raphi011/libelektra/blob/afcbcf5c8138be8de6ba9b1a9e559bc673ff887f/benchmarks/memoryleak.c#L22
ربما لا يكون هذا التسرب الصغير هو سبب ملاحظاتك.
كملاحظة جانبية: باستخدام ذاكرة التخزين المؤقت mmap ، ألاحظ حتى استهلاك أقل للذاكرة من دون ذاكرة التخزين المؤقت. ربما أحتاج إلى إضافة هذا إلى رسالتي: ابتسم :. (ذاكرة التخزين المؤقت: 200 ميجا تقريبًا مقابل عدم وجود ذاكرة تخزين مؤقت: 600 ميجا تقريبًا)
يبدو أننا "نسرب" الذاكرة عبر dlopen() / dlclose()
. يمكن ملاحظة ما يلي باستخدام kdb
ولكن ليس باستخدام kdb-static
(مجرد مقتطف):
valgrind --leak-check=full --show-leak-kinds=all ./bin/kdb ls user
[...]
==48451== 1,192 bytes in 1 blocks are still reachable in loss record 6 of 6
==48451== at 0x483AB1A: calloc (vg_replace_malloc.c:762)
==48451== by 0x400BB11: _dl_new_object (in /usr/lib64/ld-2.30.so)
==48451== by 0x400642F: _dl_map_object_from_fd (in /usr/lib64/ld-2.30.so)
==48451== by 0x4009315: _dl_map_object (in /usr/lib64/ld-2.30.so)
==48451== by 0x400DB24: openaux (in /usr/lib64/ld-2.30.so)
==48451== by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451== by 0x400DF6A: _dl_map_object_deps (in /usr/lib64/ld-2.30.so)
==48451== by 0x4013AC3: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48451== by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48451== by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48451== by 0x496139B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48451== by 0x4DFE8C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
[...]
يفتح المثال الخاص بك العديد من المقابض ، لذا يبدو كالتالي:
==48735== 72,704 bytes in 1 blocks are still reachable in loss record 8 of 8
==48735== at 0x483880B: malloc (vg_replace_malloc.c:309)
==48735== by 0x4F860A9: ??? (in /usr/lib64/libstdc++.so.6.0.27)
==48735== by 0x400FD59: call_init.part.0 (in /usr/lib64/ld-2.30.so)
==48735== by 0x400FE60: _dl_init (in /usr/lib64/ld-2.30.so)
==48735== by 0x4013DBD: dl_open_worker (in /usr/lib64/ld-2.30.so)
==48735== by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735== by 0x401363D: _dl_open (in /usr/lib64/ld-2.30.so)
==48735== by 0x48C739B: dlopen_doit (in /usr/lib64/libdl-2.30.so)
==48735== by 0x4A088C8: _dl_catch_exception (in /usr/lib64/libc-2.30.so)
==48735== by 0x4A08962: _dl_catch_error (in /usr/lib64/libc-2.30.so)
==48735== by 0x48C7B08: _dlerror_run (in /usr/lib64/libdl-2.30.so)
==48735== by 0x48C7429: dlopen@@GLIBC_2.2.5 (in /usr/lib64/libdl-2.30.so)
في المثال ، تقوم بفتح الكثير من مقابض KDB ، مما يؤدي إلى إضافة هذا التأثير إلى استهلاك كبير للذاكرة. إذا فتحت مقبض واحد فقط ، فيجب أن يكون أقل. @ raphi011 لا أعرف ما إذا كان يمكنك استخدام kdb-static
لمقاييس الأداء الخاصة بك. عند ربط المعايير بمكتبة elektra-static
بإمكاني ملاحظة نفس الشيء بعد الآن:
==54836== HEAP SUMMARY:
==54836== in use at exit: 0 bytes in 0 blocks
==54836== total heap usage: 6,456,631 allocs, 6,456,631 frees, 272,753,180 bytes allocated
==54836==
==54836== All heap blocks were freed -- no leaks are possible
==54836==
==54836== For lists of detected and suppressed errors, rerun with: -s
==54836== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
لست متأكدًا حاليًا مما يمكننا فعله حيال مشاكل dlopen()
.
هذه محاكاة مبسطة لخادم elektrad
. نظرًا لأننا نريد دعم العديد من المستخدمين لتكوين النظام في نفس الوقت ، فنحن بحاجة إلى دعم مقابض متعددة (معالجة التعارض وما إلى ذلك).
ما زلت أعتقد أن هذا ليس أصل المشكلة. المشكلة هي أن Keys / KeySets لم يتم تحريرها. إذا قمت بتغيير المفتاح الأصلي إلى "system"
والذي يحتوي على مفاتيح أقل بكثير ، فسترى أن استهلاك الذاكرة أقل بكثير.
@ markus2330 ما رأيك؟
تحرير: ننسى هذا.
لا أعتقد أننا نسرب الذاكرة هناك.
https://gist.github.com/mpranj/bbdf00af308ed3f5b3f0f35bc832756f~~
يمكنني ملاحظة نفس الشيء الذي تصفه مع HTOP واستخدام الذاكرة باستخدام الكود من الجوهر أعلاه.
إذن أنت تخبرني أنه من الطبيعي تمامًا ألا يتقلص استهلاك ذاكرة elektra أبدًا؟
إذن أنت تخبرني أنه من الطبيعي تمامًا ألا يتقلص استهلاك ذاكرة elektra أبدًا؟
أنا لا أقول ذلك. أنا أقول أنني لاحظت نفس الشيء بغض النظر عن elektra. لقد لاحظت للتو أن ملاحظتي صحيحة فقط عند الجري باستخدام valgrind ، لذلك ربما لا يكون valgrind مجانيًا حتى النهاية لأسباب أخرى.
https://stackoverflow.com/questions/40917024/memory-leak-after-using-calloc-and-free
يبدو أنه بسبب قيامك بتغيير المؤشر قبل تحريره
يبدو أنه بسبب قيامك بتغيير المؤشر قبل تحريره
أنا لا أفعل ذلك ولا يُظهر valgrind أي تسرب للرمز في الجوهر. لكن ننسى المثال ، فهو غير ذي صلة لأنه لا يتم تحريره على الفور فقط عند تشغيله داخل valgrind ، وإلا فإن الذاكرة تكون خالية () د على الفور إلى حد كبير.
ما هو أكثر صلة: لقد لاحظت ما تقوم بالإبلاغ عنه ببساطة باستخدام benchmark_createkeys
الذي لا يستخدم KDB على الإطلاق. هناك أيضًا الموارد ليست مجانية () د على الفور ، ولكن تظهر valgrind 0 تسربًا على الإطلاق. أنا محتار.
@ markus2330 ما رأيك؟
مع KeySets وحدها لن يكون هناك أي تسرب على الإطلاق. (مع KDB ، لا يمكننا التحكم في كل شيء بشكل كامل لأن المكونات الإضافية قد تقوم بتحميل مكتبات أخرى تتسرب.)
@ raphi011 هل يمكنك إنشاء توجيهي إلى مثال بسيط لإعادة إنتاج المشكلة؟
https://github.com/raphi011/libelektra/tree/memoryleak هنا تذهب. لقد وسعت المثال السابق عن طريق طباعة عدد المفاتيح التي لم يتم تحريرها بواسطة ksDel
بسبب المراجع الرئيسية > 0
.
إذا قمت بتشغيل المعيار بدون valgrind ، يمكنك أن ترى أن المفاتيح لم يتم تحريرها.
لا يمكنني تجميع هذا الريبو ، أتلقى الخطأ:
CMake Error: Error processing file: /home/markus/Projekte/Elektra/repos/libelektra/scripts/cmake/ElektraManpage.cmake
make[2]: *** [src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/build.make:61: ../doc/man/man1/kdb-elektrify-getenv.1] Fehler 1
make[1]: *** [CMakeFiles/Makefile2:17236: src/bindings/intercept/env/CMakeFiles/man-kdb-elektrify-getenv.dir/all] Fehler 2
هل يمكنك تغيير العنوان إلى السيد الرئيسي من فضلك؟
ويرجى كتابة العلاقات العامة ، فمن الأسهل بكثير رؤية التغييرات ، إذن.
إذا قمت بتشغيل المعيار بدون valgrind ، يمكنك أن ترى أن المفاتيح لم يتم تحريرها.
هل يمكنك نسخ إخراج التشغيل هنا؟
لقد لاحظت ما تقوم بالإبلاغ عنه ببساطة عن طريق استخدام benchmark_createkeys الذي لا يستخدم KDB على الإطلاق. هناك أيضًا الموارد ليست مجانية () د على الفور ، ولكن تظهر valgrind 0 تسربًا على الإطلاق. أنا محتار.
أعتقد أننا يجب أن نتبع هذا التتبع أولاً ، لأنه من الأسهل بكثير فهم ما إذا كان لدينا مجموعات مفاتيح فقط.
ربما أضفنا قمعًا خاطئًا إلى valgrind؟
كملاحظة جانبية: باستخدام ذاكرة التخزين المؤقت mmap ، ألاحظ حتى استهلاك أقل للذاكرة من دون ذاكرة التخزين المؤقت. ربما أحتاج إلى إضافة هذا إلى ابتسامة أطروحتي. (ذاكرة التخزين المؤقت: 200 ميجا تقريبًا مقابل عدم وجود ذاكرة تخزين مؤقت: 600 ميجا تقريبًا)
هذه بالتأكيد أخبار رائعة.
ربما أضفنا قمعًا خاطئًا إلى valgrind؟
أبلغت Valgrind عن عدم استخدام أي قمع عندما أجريت الاختبارات ، لكن الأميال التي قطعتها تختلف.
أعتقد أنني وجدت المشكلة خلال معاييري. بالنسبة لي ، تكمن المشكلة في التخصيص الحريص لمجموعة المفاتيح الوصفية لكل مفتاح. بعد إجراء اختبار قذر على فرعي ، إزالة تخصيص Meta KeySet الشغوف ، ينخفض استهلاك الذاكرة بسرعة بعد ksDel (). ربما تم إصلاح هذا بواسطة # 3142 لأنه يغير الكود الذي أتحدث عنه.
حسنًا ، دعنا نرى ما إذا كان # 3142 يحل المشكلة!
@ raphi011 هل
للأسف نعم
بالنسبة لي يبدو أنه على الأقل يحل / يخفف من بعض المشاكل. يمكنني الآن تشغيل الاختبار بملايين المفاتيح باستخدام ذاكرة تبلغ 2 غيغابايت تقريبًا ، بينما تعطلت من قبل بسبب استخدام ذاكرة 20 غيغابايت لنفس المعيار.
بالنسبة لي يبدو أنه على الأقل يحل / يخفف من بعض المشاكل. يمكنني الآن تشغيل الاختبار بملايين المفاتيح باستخدام ذاكرة تبلغ 2 غيغابايت تقريبًا ، بينما تعطلت من قبل بسبب استخدام ذاكرة 20 غيغابايت لنفس المعيار.
لم أقارنه بالإصدار السابق حتى الآن ، لكنني متأكد تمامًا من أنه يستخدم ذاكرة أقل من ذي قبل. سوف تحقق اليوم!
لم يتم تحرير الذاكرة رغم ذلك.
تحتاج إلى الاتصال بـ malloc_trim(0)
بعد كل free
(اقرأ: ksDel
). هذا يفرض على glibc إعادة الذاكرة على الفور إلى نظام التشغيل. يجب أن يصلح هذا "السلوك الغريب" الذي تراه يا رفاق. أوه واستمتع بالقراءة والبحث في glibc :-)
لقد أنشأت # 3183 لمزيد من الاختبارات / الإصلاح.
لقد وجدت memleak واحد (في لم يتم تحرير مفاتيح العودة kdbGet).
لكن العدد المتزايد من "ksClose لم يحرر 532 مفتاحًا" ربما يكون فقط التحذيرات التي تم جمعها في المفتاح الرئيسي. إذا جعلت NUM_RUNS أعلى ، على سبيل المثال 100 ، فإنها ستظل في حالة ركود في مرحلة ما ، لأن عدد التحذيرات يقتصر على 100. بالنسبة لي ، فإنه يصل إلى الحد الأقصى. "لم يحرر ksClose 901 مفتاحًا". سيؤدي وجود مفتاح رئيسي لكل مقبض إلى حل هذه المشكلة.
مغلق بواسطة # 3183