المدمج في الضوابط تطبيق وقت التشغيل

يعرض المنشور تطبيقًا لبرامج الأدوات المدمجة لجمع المعلومات المترية وتجميعها في وقت تشغيل التطبيقات المكتوبة في C / C ++ / C #.

يعتمد جوهر النهج الموضح على إدراج "نقاط التحكم" في رمز البرنامج الخاص بالتطبيق لاستخراج البيانات في وقت تنفيذ المكونات الهيكلية: الطرق والوظائف والكتل {}. يتم تجميع المعلومات المترية المستخرجة في قاعدة بيانات داخلية ، يتم تحويل محتوياتها في نهاية التطبيق إلى نموذج تقرير نصي يتم حفظه في ملف. ترجع ملاءمة استخدام وسائل التحكم المدمج في وقت التنفيذ إلى الحاجة إلى تحديد مجالات المشكلات في الكود ، وتحليل أسباب التدهور المؤقت للتطبيق: كامل أو جزئي ، أو يظهر على مجموعات معينة من بيانات المصدر.

توضح أمثلة التعليمات البرمجية المصدر C ++ / C # المعينة تطبيقات محتملة للنهج الموصوف.

مقدمة


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

  • تطوير واختبار الوظيفة ؛
  • تعظيم الاستفادة من الموارد المستهلكة من ذاكرة الوصول العشوائي.
  • استقرار مقاييس وقت التشغيل.

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

أحد الأساليب الفعالة لحل مشكلة تحليل مقاييس وقت التطبيق هو استخدام منتجات البرمجيات المتخصصة ، على سبيل المثال GNU gprof . يسمح لك تحليل التقارير التي تم إنشاؤها بواسطة هذه الأدوات بتحديد "الاختناقات" (الأساليب والوظائف الطبقية) ، والتي تمثل مقدارًا كبيرًا من الوقت الذي يستغرقه تنفيذ التطبيق ككل. في الوقت نفسه ، بالتأكيد مؤهل المطورين صحة الوقت الذي يقضيه في تنفيذ الأساليب والإجراءات.

تجدر الإشارة أيضًا إلى أن منتجات البرامج من هذه الفئة ، كقاعدة عامة ، تجري تحليلًا متريًا لوقت تنفيذ رمز البرنامج على مستويات أساليب الفئات والوظائف ، مع تجاهل المستويات الأدنى (ولكن مع ذلك من وجهة نظر تحليل المشكلة): {...} ، من أجل في حين ، القيام ، حتى ، إذا - آخر ، كتل محاولة الصيد ، والتي داخلها لا تقل النفقات الهامة وقت التنفيذ.

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

طرق لاسترجاع بيانات وقت التشغيل


يمكن تفسير وظيفة أي تطبيق برنامج على أنه آلة مجردة مع مجموعة محدودة من الحالات الفريدة {St} والانتقالات {Tr} بينهما.

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

إن استخراج وتراكم البيانات في وقت تنفيذ التطبيق عند نقاط التحكم المحددة هي المهمة الرئيسية التي يتم حلها بواسطة أدوات التحكم المدمجة الموضحة أدناه.

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

يتم التحكم في نقاط التحكم (التسجيل الأولي وحساب مقاييس وقتهم) بواسطة كائن "timeManager" ، الذي يتم إنشاؤه في مثيل واحد. يتم تسجيل كل حدث لاجتياز نقطة التحكم بواسطة الكائن "timeManager" ، وخلاله يتم تسجيله من خلال المقطع الأول كملاحظات مثل "registerEntry" .

في كل لحظة تمر من نقطة التحكم ، يتم إنشاء كائن timerObject ، وتحديد وقت إنشائه. يتم إصلاح وقت التنفيذ عند نقطة التفتيش عند خروج التطبيق من المستوى الحالي للتسلسل الهرمي للبرنامج. في هذه اللحظة ، يتم إتلاف timerObject الكائن تلقائيًا ، والذي يكون مصحوبًا بحساب "العمر" الخاص به T. ونتيجة لذلك ، يزيد timeManager من عدد مرات مرور نقطة التفتيش والوقت الذي يقضيه T. بالنسبة لجميع نقاط التحكم المحددة ، يقوم timeManager بتجميع البيانات مع الإصدار اللاحق للتقرير عند إنهاء التطبيق.



يوجد أدناه رمز C ++ المصدر الذي يقوم بتنفيذ الأدوات المضمنة للتحكم في وقت تنفيذ التطبيق.

//     #include <vector> #include <map> #include <algorithm> #include <stdio.h> #include <time.h> typedef unsigned long LONGEST_INT; // ,    //     //     //  ( )   // ()   //    #define PROFILER_ENABLED // CREATE_PROFILER  timeManager , //     // 'main()' #ifdef PROFILER_ENABLED #define CREATE_PROFILER timeManager tManager; #else #define CREATE_PROFILER //   CREATE_PROFILER. #endif //INITIALIZE_PROFILER    timeManager  //       //   'main()' #ifdef PROFILER_ENABLED #define INITIALIZE_PROFILER bool timeManager::object = false;\ std::vector<registerEntry> timeManager::entries; #else #define INITIALIZE_PROFILER //   INITIALIZE_PROFILER. #endif //DELAY(_SECONDS)   '_SECONDS' . //    ,  //     //  #ifdef PROFILER_ENABLED #define DELAY(_SECONDS) {clock_t clk_wait=clock()+((double)_ARG)*CLOCKS_PER_SEC;\ while(clock()<clk_wait) {}} #else #define DELAY(_SECONDS) //    DELAY. #endif //     , //     UNIX  WINDOWS //      #ifdef PROFILER_ENABLED #define MERGE2(x,y) x##y #define MERGE1(_X,_Y) MERGE2(_X,_Y) #if WIN32 #define UNIQUENAME prefix,postfix) MERGE1(prefix,postfix) #else #define UNIQUENAME(prefix,postfix) MERGE2(prefix,postfix) #endif #define GEN_SRC(_ARG1,_ARG2) static int UNIQUENAME(ind,_ARG2)=-1;\ if(UNIQUENAME(ind,_ARG2)<0)\ UNIQUENAME(ind,_ARG2)=timeManager::add_entry(_ARG1);\ timeManager::incr_counter(UNIQUENAME(ind,_ARG2));\ timerObject UNIQUENAME(tm,_ARG2)(UNIQUENAME(ind,_ARG2)); //PROFILE_ENTRY      #ifdef PROFILER_ENABLED #if WIN32 #define PROFILE_ENTRY(_TITLE) GEN_SRC(_TITLE,__COUNTER__) #else #define PROFILE_ENTRY(_TITLE) GEN_SRC(_TITLE,__LINE__) #endif #else #define PROFILE_ENTRY(_TITLE) //    PROFILE_ENTRY. #endif //        //    //    ,   timeManager struct registerEntry { //     (  ) std::string entry_name; //     //     LONGEST_INT covers_counter; //      //     (ticks) LONGEST_INT elapsed_time; // registerEntry(const char * title):entry_name(title), covers_counter(0), elapsed_time(0) {} }; //     class timerObject { //   ,     int index; //    clock_t start_time; public: //       timerObject(int ind):index(ind),start_time(clock()) {} //    “ ”  //       //   ~timerObject(void) { timeManager::incr_timer(index,(LONGEST_INT)(clock()-start_time)); } }; //     class timeManager { private: //     static std::vector<registerEntry> entries; // ,     //    static bool object; public: //     //  ,    //     static int add_entry(const char * title) { entries.push_back(registerEntry(title)); return (((int)entries.size())-1); } //       //      static void incr_counter(int profile_entry_id) { entries[profile_entry_id].covers_counter++; } //  'value'     //      static void incr_timer(int profile_entry_id, LONGEST_INT value) { entries[profile_entry_id].elapsed_time += val; } //       //   static void report(void); //  timeManager(void) { if(!object) object = true; else { printf("\n<<>>:    'timeManager' .\n"); throw; } } //        //   virtual ~timeManager(void) {report();} }; //      bool cmp_entries(registerEntry & first, registerEntry & second) { if(first.entry_name.compare(second.entry_name)>0) return false; return true; } //      //    void timeManager::report(void) { const std::string bar(72,'*'); //        const char * REPORT_FILE = "C:\\tmProfile.txt"; FILE * fp = fopen(REPORT_FILE,"w"); if(!fp) { printf("\n<<>>:       (%s)",REPORT_FILE); return; } fprintf(fp,"\n#%s",bar.c_str()); fprintf(fp,"\n#\n#      "); fprintf(fp,"\n#\n#%s",bar.c_str()); fprintf(fp,"\n#\n# %-35s %-15s %-20s", " ",""," ()"); fprintf(fp,"\n# %-35s %-15s %-20s", "------------------","-------------","---------------\n#"); //         std::sort(entries.begin(),entries.end(),cmp_entries); for(unsigned jj = 0; jj< entries.size(); jj++) { fprintf(fp,"\n# %-35s %-16d", entries[jj].entry_name.c_str(), entries[jj].covers_counter); if(entries[jj].covers_counter == 0) fprintf(fp,"%-20d",0); else fprintf(fp,"%-20.0f", static_cast<double>(entries[jj].elapsed_time)/ static_cast<double>(CLOCKS_PER_SEC)); } if(entries.size() == 0) fprintf(fp,"\n# No covered profile entries found\n"); fprintf(fp,"\n#\n#%s\n",bar.c_str()); fclose(fp); } 

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





القسم الملحق 2. الكود المصدري لوسائل التحكم المضمن لوقت تنفيذ التطبيق C # يقدم تنفيذ مشابه لوسائل التحكم المدمج في C #.

يستخدم المؤلف أزواجًا من TimeWatcher.StartWatch () و TimeWatcher.StopWatch () عند تحديد وقت تنفيذ الأساليب والإجراءات الشاقة (من وجهة نظر حسابية) كجزء من منتج برنامج Delta Design الذي تم تطويره بواسطة EREMEX - نظام تصميم بمساعدة الكمبيوتر للمعدات الإلكترونية.

فيما يلي مثال لتقرير موجز عن مقاييس الوقت لإحدى وظائف المنتج المذكور.


استنتاجات موجزة


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

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

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

الملحق 1. شفرة المصدر للتطبيق التجريبي


 INITIALIZE_PROFILER int main(int argc, char * argv[]) { //create profile manager CREATE_PROFILER PROFILE_ENTRY("1 Main context") f11(); for(unsigned jj = 0;jj<4;jj++) f12(); f13 (); f14 (); f15 (); f16 (); f17(); return 0; } void f11(void)///////////////////////////////////////// { PROFILE_ENTRY ("2 f11()........................") for (unsigned jj = 0; jj<5; jj++) { PROFILE_ENTRY ("2 f11()::for(...){...} iterat-ing") DELAY(1) } //profile entry for repeating int nn(3); while(nn > 0) { PROFILE_ENTRY("2 f11()::while(...){...} iterat-ing") DELAY(1) nn--; } } void f12(void)///////////////////////////////////////// { PROFILE_ENTRY("3 f12()........................") goto ending; { PROFILE_ENTRY("3 f12()::ignored code part") DELAY(1) } ending: PROFILE_ENTRY("3 f12()::ending code part") DELAY(2) } void f13(void) ///////////////////////////////////////// { PROFILE_ENTRY("4 f13()........................") srand((unsigned) time(NULL)/2); for(unsigned jj = 0; jj < 200; jj++) { if(rand()%2 == 0) { PROFILE_ENTRY("4 f13()::even branch") DELAY(0.01) } else { PROFILE_ENTRY("4 f13()::od branch") DELAY(0.02) } } } void f14(void)///////////////////////////////////////// { static int depth = 10; { PROFILE_ENTRY("5 f14() recursion") depth--; DELAY(0.5) if(depth == 0) return; } f14(); } void f15(void)///////////////////////////////////////// { PROFILE_ENTRY("7 f15()........................") for(unsigned jj = 0; jj < 10; jj++) { demo_class obj; obj.method1(); obj.method2(); obj.method3(); } } void f16(void)///////////////////////////////////////// { PROFILE_ENTRY("8 f16()........................") try { for(int jj = 10; jj >= 0; jj--) { PROFILE_ENTRY("81 f16() try clause") DELAY(1) int rr = 200/jj; } } catch(...) { PROFILE_ENTRY("81 f16() catch clause") DELAY(2) return; } } void f17(void)///////////////////////////////////////// { PROFILE_ENTRY("9 f17()........................") f21(); f22(); f23(); f24(); f25(); } void f22(void)///////////////////////////////////////// { PROFILE_ENTRY("91 f22()........................") DELAY(1) f221(); f222(); f223(); } void f23(void) {PROFILE_ENTRY("91 f23()") DELAY(1) } void f24(void) {PROFILE_ENTRY("91 f24()") DELAY(1) } void f25(void) {PROFILE_ENTRY("91 f25()") DELAY(1) } void f221(void) {PROFILE_ENTRY("91 f221()") DELAY(3) } void f222(void) {PROFILE_ENTRY("91 f222()") DELAY(4) } void f223(void) {PROFILE_ENTRY("91 f223()") DELAY(5) } 

الملحق 2. شفرة المصدر للتحكم في وقت التشغيل المدمج في تطبيقات C #


 /// <summary> ///             /// </summary> public class TimeWatcher { /// <summary> ///            /// </summary> internal class TimeEntry { //     public Stopwatch timeWatch; //       public long elapsedTime; //  public TimeEntry() { timeWatch = new Stopwatch(); elapsedTime = 0; } } //       //    private static bool enableTimeWatcher = false; //            private static Dictionary<string, TimeEntry> entryDictionary = new Dictionary<string, TimeEntry>(); //         public static void StartWatch(string postfix = "") { if (!enableTimeWatcher) return; string entryName = GetCurrentMethod(); if (postfix != "") { entryName += postfix; } //    ,      //         if (!entryDictionary.ContainsKey(entryName)) { entryDictionary.Add(entryName, new TimeEntry()); entryDictionary[entryName].timeWatch.Start(); } else { if (entryDictionary[entryName].timeWatch.IsRunning) { throw new System.InvalidOperationException(":    '" + entryName + "'  ."); } else entryDictionary[entryName].timeWatch.Restart(); } } //        public static void StopWatch(string postfix = "") { if (!enableTimeWatcher) return; string entryName = GetCurrentMethod(); if (postfix != "") { entryName += postfix; } //    ,      if (!entryDictionary.ContainsKey(entryName)) { throw new System.InvalidOperationException(":     -    '" + entryName + "'."); } if (!entryDictionary[entryName].timeWatch.IsRunning) { throw new System.InvalidOperationException ":        '" + entryName + "'."); } entryDictionary[entryName].timeWatch.Stop(); entryDictionary[entryName].elapsedTime += entryDictionary[entryName].timeWatch.ElapsedMilliseconds; } //        //     public static void TimeWatchReport() { const string bar = "============================================="; if (!enableTimeWatcher) return; Console.WriteLine(""); Console.WriteLine(bar); Console.WriteLine("     (): "); Console.WriteLine(""); int maxLen = 0; foreach (var timeEntry in entryDictionary) { if(timeEntry.Key.Length > maxLen) maxLen = timeEntry.Key.Length; } maxLen++; string strFormat = "{0," + maxLen + "} ... {1,-10}"; foreach (var timeEntry in entryDictionary) { Console.WriteLine(strFormat, timeEntry.Key, timeEntry.Value.elapsedTime); } Console.WriteLine(bar); Console.WriteLine(""); entryDictionary.Clear(); enableTimeWatcher = false; } //          //      /tmw    //   public static void InitTimeWatch() { if (Environment.GetCommandLineArgs().Any(v => v == "/tmw")) { if (entryDictionary.Count > 0) { TimeWatchReport(); } entryDictionary.Clear(); enableTimeWatcher = true; } } //        private static string GetCurrentMethod() { StackTrace st = new StackTrace(); StackFrame sf = st.GetFrame(2); return sf.GetMethod().Name; } } 

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


All Articles