ميزات برامج ملفات التعريف في C ++


في بعض الأحيان ، قد يكون من الضروري تحديد أداء البرنامج أو استهلاك الذاكرة في برنامج C ++. لسوء الحظ ، في كثير من الأحيان هذا ليس سهلا كما قد يبدو.

سننظر هنا في ميزات برامج ملفات التعريف باستخدام أدوات valgrind و google perftools . تحولت المادة إلى أنها ليست منظمة للغاية ، بل هي محاولة لتجميع قاعدة معرفية "للأغراض الشخصية" بحيث لا يتعين عليك في المستقبل أن تتذكر "لماذا لا ينجح هذا" أو "كيفية القيام بذلك". على الأرجح ، لن تتأثر جميع الحالات غير الواضحة هنا ، إذا كان لديك شيء تضيفه ، فالرجاء كتابة التعليقات.

جميع الأمثلة سوف تعمل على نظام لينكس.

وقت التشغيل التنميط


تدريب


لتحليل ميزات التوصيف ، سأقوم بتشغيل برامج صغيرة ، تتكون عادةً من ملف main.cpp وملف func.cpp واحد مع التضمين.
سوف أجمعها مع برنامج التحويل البرمجي g ++ 8.3.0 .

نظرًا لأن إنشاء ملفات غير محسّنة مهمة غريبة إلى حد ما ، فسنقوم بتجميع خيار -Ofast ، ومن أجل الحصول على أحرف تصحيح الأخطاء في الإخراج ، لن ننسى إضافة خيار -g . ومع ذلك ، في بعض الأحيان بدلاً من أسماء الوظائف العادية ، يمكنك رؤية عناوين المكالمات غير المسموعة فقط. هذا يعني أنه كان هناك "توزيع عشوائي لتخصيص مساحة العنوان". يمكن تحديد ذلك عن طريق استدعاء الأمر nm على ثنائي. إذا كانت معظم العناوين تبدو مثل هذا 00000000000030e0 (عدد كبير من الأصفار في البداية) ، فمن المرجح أن هذا هو. في البرنامج العادي ، تبدو العناوين مثل 0000000000402fa0. لذلك ، تحتاج إلى إضافة الخيار -no-pie . نتيجة لذلك ، ستبدو المجموعة الكاملة للخيارات كما يلي:
-Ofast -g -no-pie

لعرض النتائج ، سوف نستخدم برنامج KCachegrind ، والذي يمكنه العمل مع تنسيق تقرير callgrind

Callgrind


الأداة الأولى التي سننظر إليها اليوم هي callgrind . هذه الأداة هي جزء من أداة valgrind. إنه يحاكي كل تعليمات قابلة للتنفيذ من البرنامج ، وعلى أساس المقاييس الداخلية حول "تكلفة" كل تعليمات ، يصدر الاستنتاج الذي نحتاجه. بسبب هذا النهج ، يحدث في بعض الأحيان أن callgrind لا يمكن أن يتعرفوا على التعليمات التالية ويحدث خطأ
تعليمات غير معترف بها في العنوان
السبيل الوحيد للخروج من هذا الموقف هو إعادة النظر في جميع خيارات الترجمة ومحاولة العثور على التداخل

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

static_lib
////////////////// // static_lib.h // ////////////////// #ifndef SERVER_STATIC_LIB_H #define SERVER_STATIC_LIB_H int func_static_lib(int arg); #endif //SERVER_STATIC_LIB_H //////////////////// // static_lib.cpp // /////////////////// #include "static_lib.h" #include "static_func.h" #include <cstddef> int func_static_lib(int arg) { return static_func(arg); } /////////////////// // static_func.h // /////////////////// #ifndef TEST_PROFILER_STATIC_FUNC_H #define TEST_PROFILER_STATIC_FUNC_H int static_func(int arg); #endif //TEST_PROFILER_STATIC_FUNC_H ///////////////////// // static_func.cpp // ///////////////////// #include "static_func.h" int static_func(int arg) { int fst = 0; int snd = 1; for (int i = 0; i < arg; i++) { int tmp = (fst + snd) % 17769897; fst = snd; snd = tmp; } return fst; } 


shared_lib
 ////////////////// // shared_lib.h // ////////////////// #ifndef TEST_PROFILER_SHARED_LIB_H #define TEST_PROFILER_SHARED_LIB_H int func_shared_lib(int arg); #endif //TEST_PROFILER_SHARED_LIB_H //////////////////// // shared_lib.cpp // //////////////////// #include "shared_lib.h" #include "shared_func.h" int func_shared_lib(int arg) { return shared_func(arg); } /////////////////// // shared_func.h // /////////////////// #ifndef TEST_PROFILER_SHARED_FUNC_H #define TEST_PROFILER_SHARED_FUNC_H int shared_func(int arg); #endif //TEST_PROFILER_SHARED_FUNC_H ///////////////////// // shared_func.cpp // ///////////////////// #include "shared_func.h" int shared_func(int arg) { int result = 1; for (int i = 1; i < arg; i++) { result = (int)(((long long)result * i) % 19637856977); } return result; } 


رئيسي
 ////////////// // main.cpp // ////////////// #include <iostream> #include "static_lib.h" #include "shared_lib.h" #include "func.h" int main(int argc, char **argv) { if (argc != 2) { std::cout << "Incorrect args"; return -1; } const int arg = std::atoi(argv[1]); std::cout << "result: " << func_static_lib(arg) << " " << func_shared_lib(arg) << " " << func(arg); return 0; } //////////// // func.h // //////////// #ifndef TEST_PROFILER_FUNC_H #define TEST_PROFILER_FUNC_H int func(int arg); #endif //TEST_PROFILER_FUNC_H ////////////// // func.cpp // ////////////// #include "func.h" int func(int arg) { int fst = 1; int snd = 1; for (int i = 0; i < arg; i++) { int res = (fst + snd + 1) % 19845689; fst = snd; snd = res; } return fst; } 


نقوم بتجميع البرنامج ، وتشغيل valgrind على النحو التالي:

 valgrind --tool=callgrind ./test_profiler 100000000 


نرى أنه بالنسبة للمكتبة الثابتة والوظائف المنتظمة ، فإن النتيجة مشابهة لتلك المتوقعة. ولكن في المكتبة الديناميكية ، تعذر على callgrind حل الوظيفة بالكامل.

لإصلاح ذلك ، عند بدء تشغيل البرنامج ، تحتاج إلى تعيين متغير LD_BIND_NOW على 1 ، مثل هذا:

 LD_BIND_NOW=1 valgrind --tool=callgrind ./test_profiler 100000000 


والآن ، كما ترون ، كل شيء على ما يرام

مشكلة callgrind التالية الناشئة عن التنميط عن طريق محاكاة الإرشادات هي أن تنفيذ البرنامج يبطئ كثيرا. قد يحمل هذا تقديرًا نسبيًا غير صحيح لوقت تنفيذ أجزاء مختلفة من التعليمات البرمجية.

لنلقِ نظرة على هذا الكود:

 int func(int arg) { int fst = 1; int snd = 1; std::ofstream file("tmp.txt"); for (int i = 0; i < arg; i++) { int res = (fst + snd + 1) % 19845689; std::string r = std::to_string(res); file << res; file.flush(); fst = snd; snd = res + r.size(); } return fst; } 

أضفت هنا كمية صغيرة من البيانات إلى ملف لكل تكرار للحلقة. بما أن الكتابة إلى ملف هي عملية طويلة إلى حد ما ، كوزن موازن ، أضفت خطًا من رقم إلى كل تكرار للحلقة. من الواضح ، في هذه الحالة ، تستغرق عملية الكتابة إلى الملف وقتًا أطول من باقي منطق الوظيفة. لكن callgrind يفكر بطريقة مختلفة:


يجدر أيضًا مراعاة أن callgrind يمكنه فقط قياس تكلفة وظيفة عندما تعمل. لا تعمل الوظيفة - وبالتالي ، لا تزيد التكلفة. يؤدي هذا إلى تعقيد تصحيح البرامج التي تدخل القفل من وقت لآخر أو تعمل مع نظام / شبكة لحظر الملفات. لنفحص:

 #include "func.h" #include <mutex> static std::mutex mutex; int funcImpl(int arg) { std::lock_guard<std::mutex> lock(mutex); int fst = 1; int snd = 1; for (int i = 0; i < arg; i++) { int res = (fst + snd + 1) % 19845689; fst = snd; snd = res; } return fst; } int func2(int arg){ return funcImpl(arg); } int func(int arg) { return funcImpl(arg); } int main(int argc, char **argv) { if (argc != 2) { std::cout << "Incorrect args"; return -1; } const int arg = std::atoi(argv[1]); auto future = std::async(std::launch::async, &func2, arg); std::cout << "result: " << func(arg) << std::endl; std::cout << "second result " << future.get() << std::endl; return 0; } 

هنا قمنا بتضمين تنفيذ الوظيفة بالكامل في قفل mutex ، وسميت هذه الوظيفة من خيطين مختلفين. يمكن التنبؤ بنتيجة callgrind تمامًا - فهو لا يرى مشكلة في التقاط كائن مزامنة:


لذلك ، درسنا بعض مشاكل استخدام منشئ ملفات التعريف callgrind. دعنا ننتقل إلى موضوع الاختبار التالي - ملف تعريف google perftools

جوجل perftools


على عكس callgrind ، يعمل google profiler على مبدأ مختلف.
بدلاً من تحليل كل إرشادات للبرنامج القابل للتنفيذ ، يقوم بإيقاف البرنامج مؤقتًا على فترات منتظمة ويحاول تحديد الوظيفة الموجودة عليه حاليًا. نتيجة لذلك ، لا يؤثر هذا تقريبًا على أداء التطبيق قيد التشغيل. ولكن هذا النهج أيضا نقاط ضعفه.

لنبدأ بالتوصيف على البرنامج الأول مع مكتبتين.

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

gprof.sh
 rnd=$RANDOM if [ $# -eq 0 ] then echo "./gprof.sh command args" echo "Run with variable N_STOP=true if hand stop required" exit fi libprofiler=$( dirname "${BASH_SOURCE[0]}" ) arg=$1 nostop=$N_STOP profileName=callgrind.out.$rnd.g gperftoolProfile=./gperftool."$rnd".txt touch $profileName echo "Profile name $profileName" if [[ $nostop = "true" ]] then echo "without stop" trap 'echo trap && kill -12 $PID && sleep 1 && kill -TERM $PID' TERM INT else trap 'echo trap && kill -TERM $PID' TERM INT fi if [[ $nostop = "true" ]] then CPUPROFILESIGNAL=12 CPUPROFILE_FREQUENCY=1000000 CPUPROFILE=$gperftoolProfile LD_PRELOAD=${libprofiler}/libprofiler.so "${@:1}" & else CPUPROFILE_FREQUENCY=1000000 CPUPROFILE=$gperftoolProfile LD_PRELOAD=${libprofiler}/libprofiler.so "${@:1}" & fi PID=$! if [[ $nostop = "true" ]] then sleep 1 kill -12 $PID fi wait $PID trap - TERM INT wait $PID EXIT_STATUS=$? echo $PWD ${libprofiler}/pprof --callgrind $arg $gperftoolProfile* > $profileName echo "Profile name $profileName" rm -f $gperftoolProfile* 


يجب تشغيل هذه الأداة المساعدة ، لتمرير اسم الملف القابل للتنفيذ وقائمة المعلمات الخاصة به كمعلمات. أيضًا ، من المفترض أن بجانب الملفات النصية الملفات التي يحتاجها libprofiler.so و pprof. إذا كان البرنامج طويل العمر وتوقف عن طريق مقاطعة التنفيذ ، يجب عليك تعيين المتغير N_STOP إلى صواب ، على سبيل المثال ، مثل هذا:
 N_STOP=true ./gprof.sh ./test_profiler 10000000000 

في نهاية العمل ، سينشئ النص تقريرًا بتنسيق callgrind المفضل لدي.

لذلك ، دعونا ندير برنامجنا تحت هذا التعريف.
 ./gprof.sh ./test_profiler 1000000000 


من حيث المبدأ ، كل شيء واضح جدا.

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

 int runExcept(int res) { if (res % 13 == 0) { throw std::string("Exception"); } return res; } int func(int arg) { int fst = 1; int snd = 1; for (int i = 0; i < arg; i++) { int res = (fst + snd + 1) % 19845689; try { res = runExcept(res); } catch (const std::string &e) { res = res - 1; } fst = snd; snd = res; } return fst; } 

وتشغيل التنميط. البرنامج يتجمد بسرعة كبيرة.

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

 #include "func.h" static int func1(int arg) { std::cout << 1 << std::endl; return func(arg); } static int func2(int arg) { std::cout << 2 << std::endl; return func(arg); } static int func3(int arg) { std::cout << 3 << std::endl; if (arg % 2 == 0) { return func2(arg); } else { return func1(arg); } } int main(int argc, char **argv) { if (argc != 2) { std::cout << "Incorrect args"; return -1; } const int arg = std::atoi(argv[1]); int arg2 = func3(arg); int arg3 = func(arg); std::cout << "result: " << arg2 + arg3; return 0; } 

من الواضح ، إذا قمت بتشغيل البرنامج على سبيل المثال مثل هذا:
 ./gprof.sh ./test_profiler 1000000000 

ثم لن يتم استدعاء الدالة func1. لكن المحلل يفكر بطريقة مختلفة:

(بالمناسبة ، قرر valgrind هنا التزام الصمت بشكل متواضع وعدم تحديد الوظيفة المحددة التي جاءت منها المكالمة).

التنميط الذاكرة


غالبًا ما تكون هناك مواقف عندما تتدفق الذاكرة من التطبيق في مكان ما. إذا كان هذا بسبب نقص تنظيف الموارد ، فيجب أن تساعد Memcheck في تحديد المشكلة. لكن في C ++ الحديثة ، ليس من الصعب الاستغناء عن الإدارة اليدوية للموارد. unique_ptr، shared_ptr، vector، map تجعل التلاعب بالنقاط المجردة بلا فائدة.

ومع ذلك ، في مثل هذه التطبيقات ، يحدث تسرب للذاكرة. كيف الحال؟ بكل بساطة ، كقاعدة عامة ، هو شيء مثل "ضع القيمة في خريطة طويلة العمر ، ولكن نسيت حذفها". دعونا نحاول تتبع هذا الموقف.

للقيام بذلك ، نعيد كتابة وظيفة الاختبار الخاصة بنا بهذه الطريقة

 #include "func.h" #include <deque> #include <string> #include <map> static std::deque<std::string> deque; static std::map<int, std::string> map; int func(int arg) { int fst = 1; int snd = 1; for (int i = 0; i < arg; i++) { int res = (fst + snd + 1) % 19845689; fst = snd; snd = res; deque.emplace_back(std::to_string(res) + " integer"); map[i] = "integer " + std::to_string(res); deque.pop_front(); if (res % 200 != 0) { map.erase(i - 1); } } return fst; } 

هنا ، في كل تكرار ، نضيف بعض العناصر إلى الخريطة ، وننسى أنه في بعض الأحيان يتم حذفها من هناك عن طريق الصدفة (صحيح ، صحيح). أيضا ، لتجنب عيوننا ، نحن التعذيب الأمراض المنقولة جنسيا :: deque قليلا.

سنلتقط تسريبات الذاكرة باستخدام أداتين - valgrind massif و google heapdump .

المسيف الجزء الرئيسي من جبل


قم بتشغيل البرنامج باستخدام هذا الأمر
 valgrind --tool=massif ./test_profiler 1000000 

ونحن نرى شيئا مثل

المسيف الجزء الرئيسي من جبل
 time=1277949333 mem_heap_B=313518 mem_heap_extra_B=58266 mem_stacks_B=0 heap_tree=detailed n4: 313518 (heap allocation functions) malloc/new/new[], --alloc-fns, etc. n1: 195696 0x109A69: func(int) (new_allocator.h:111) n0: 195696 0x10947A: main (main.cpp:18) n1: 72704 0x52BA414: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25) n1: 72704 0x4010731: _dl_init (dl-init.c:72) n1: 72704 0x40010C8: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so) n1: 72704 0x0: ??? n1: 72704 0x1FFF0000D1: ??? n0: 72704 0x1FFF0000E1: ??? n2: 42966 0x10A7EC: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (new_allocator.h:111) n1: 42966 0x10AAD9: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) (basic_string.tcc:466) n1: 42966 0x1099D4: func(int) (basic_string.h:1932) n0: 42966 0x10947A: main (main.cpp:18) n0: 0 in 2 places, all below massif's threshold (1.00%) n0: 2152 in 10 places, all below massif's threshold (1.00%) 


يمكن أن نرى أن كتلة صخرية كانت قادرة على اكتشاف تسرب في الوظيفة ، لكن حتى الآن لم يتضح أين. دعنا نعيد بناء البرنامج مع العلم -fno-inline وتشغيل التحليل مرة أخرى

المسيف الجزء الرئيسي من جبل
 time=3160199549 mem_heap_B=345142 mem_heap_extra_B=65986 mem_stacks_B=0 heap_tree=detailed n4: 345142 (heap allocation functions) malloc/new/new[], --alloc-fns, etc. n1: 221616 0x10CDBC: std::_Rb_tree_node<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >* std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<int const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<int const&>&&, std::tuple<>&&) [clone .isra.81] (stl_tree.h:653) n1: 221616 0x10CE0C: std::_Rb_tree_iterator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<int const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::piecewise_construct_t const&, std::tuple<int const&>&&, std::tuple<>&&) [clone .constprop.87] (stl_tree.h:2414) n1: 221616 0x10CF2B: std::map<int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::operator[](int const&) (stl_map.h:499) n1: 221616 0x10A7F5: func(int) (func.cpp:20) n0: 221616 0x109F8E: main (main.cpp:18) n1: 72704 0x52BA414: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25) n1: 72704 0x4010731: _dl_init (dl-init.c:72) n1: 72704 0x40010C8: ??? (in /lib/x86_64-linux-gnu/ld-2.27.so) n1: 72704 0x0: ??? n1: 72704 0x1FFF0000D1: ??? n0: 72704 0x1FFF0000E1: ??? n2: 48670 0x10B866: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (basic_string.tcc:317) n1: 48639 0x10BB2C: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) (basic_string.tcc:466) n1: 48639 0x10A643: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&) [clone .constprop.86] (basic_string.h:6018) n1: 48639 0x10A7E5: func(int) (func.cpp:20) n0: 48639 0x109F8E: main (main.cpp:18) n0: 31 in 1 place, below massif's threshold (1.00%) n0: 2152 in 10 places, all below massif's threshold (1.00%) 


أصبح من الواضح الآن مكان التسرب في إضافة عنصر الخريطة. يمكن لـ Massif اكتشاف أشياء قصيرة العمر ، لذا فإن التلاعب بالأمراض المنقولة جنسياً :: deque غير مرئي في هذا التفريغ.

Heapdump


لكي يعمل google heapdump ، يلزمك ربط مكتبة tcmalloc أو تحميلها مسبقًا . تحل هذه المكتبة محل الوظائف المعيارية لتخصيص الذاكرة malloc ، مجانًا ، ... كما يمكنها جمع معلومات حول استخدام هذه الوظائف ، والتي سنستخدمها عند تحليل البرنامج.

نظرًا لأن هذه الطريقة تعمل ببطء شديد (حتى بالمقارنة مع الكتلة) ، فإنني أوصيك بتعطيل تجميع الوظائف على الفور باستخدام الخيار -fno -line عند التحويل البرمجي. لذلك ، نحن نعيد بناء طلبنا ونعمل مع الفريق
 HEAPPROFILESIGNAL=23 HEAPPROFILE=./heap ./test_profiler 100000000 

هنا يفترض أن مكتبة tcmalloc مرتبطة بطلبنا.

الآن ، ننتظر بعض الوقت اللازم لتشكيل تسرب ملحوظ ، ونرسل إلى العملية إشارة 23
 kill -23 <pid> 

نتيجة لذلك ، يظهر ملف يسمى heap.0001.heap ، نقوم بتحويله إلى تنسيق callgrind باستخدام الأمر

 pprof ./test_profiler "./heap.0001.heap" --inuse_space --callgrind > callgrind.out.4 

أيضا الانتباه إلى خيارات pprof. يمكنك الاختيار من بين الخيارات inuse_space أو inuse_objects أو custom_space أو custom_objects ، والتي تعرض المساحة أو الكائنات المستخدمة ، أو المساحة والكائنات المخصصة لكامل مدة البرنامج ، على التوالي. نحن مهتمون بخيار inuse_space ، والذي يعرض مساحة الذاكرة المستخدمة حاليًا.

فتح kCacheGrind المفضلة لدينا ونرى

الأمراض المنقولة جنسيا :: خريطة قد أكلت الكثير من الذاكرة. ربما تسرب فيه.

النتائج


التوصيف في C ++ مهمة صعبة للغاية. هنا يتعين علينا التعامل مع الدوال المضمنة ، والتعليمات غير المدعومة ، والنتائج غير الصحيحة ، إلخ. ليس من الممكن دائمًا الوثوق بنتائج ملف التعريف.

بالإضافة إلى الوظائف المقترحة أعلاه ، هناك أدوات أخرى مصممة للتنميط - perf و intel VTune وغيرها. لكنها تظهر أيضا بعض هذه العيوب. لذلك ، لا تنسى طريقة "الجد" الخاصة بالتوصيف عن طريق قياس وقت تنفيذ الوظائف وعرضها في السجل.

أيضا ، إذا كان لديك تقنيات مثيرة للاهتمام لتعريف رمز الخاص بك ، يرجى نشرها في التعليقات

Source: https://habr.com/ru/post/ar482040/


All Articles