内置软件应用程序运行时控件

该出版物介绍了内置工具的软件实现,这些工具用于收集和累积用C / C ++ / C#编写的应用程序运行时的度量标准信息。

所描述方法的实质是基于在应用程序的程序代码中包含“控制点”,用于提取有关结构组件(方法,函数和{}块)执行时间的数据。 提取的度量标准信息将存储在内部数据库中,其内容在应用程序末尾转换为保存在文件中的文本报告形式。 使用内置控制执行时间的方法的适当性是由于需要识别代码的问题区域,分析应用程序暂时降级的原因:全部或部分,或表现在某些源数据集上。

给定的C ++ / C#源代码示例演示了所描述方法的可能实现。

引言


在软件开发的每次迭代(例如,下一版本的发行)中进行软件应用程序的开发包括以下基本步骤:

  • 功能开发和测试;
  • 优化RAM的消耗资源;
  • 运行时指标的稳定化。

这些步骤需要大量的开发人员,不仅需要创造性的工作(例如有效算法的开发和实现,构建灵活的软件体系结构等),而且还需要日常工作。 后一类包括旨在稳定应用程序执行时间指标的活动。 在许多情况下,当开发人员面临性能下降时,这是一个相当痛苦的过程,这是扩展软件产品的功能,重建软件体系结构以及应用程序中出现新线程的结果。 同时,退化源需要付出一定的努力来检测它们,这不仅通过开发人员的高度勤奋和责任感(必要条件),而且还通过用于这些目的的工具组成(充分条件)来实现。

解决应用程序时间指标问题的一种有效方法是使用专用软件产品,例如GNU gprof 。 通过此类工具生成的报告分析,您可以识别“瓶颈”(类方法和函数),这些瓶颈占执行整个应用程序所花费的大量时间。 同时,在方法和过程的执行上花费的时间的有效性肯定由开发人员确定。

还应注意,该类的软件产品通常在类和函数的方法级别上对程序代码的执行时间进行度量分析,而忽略以下级别(但从问题分析的角度来看仍然很重要): {...}同时,直到try-catch一直执行到执行,否则在其中发生不少于执行时间的开销。

接下来,考虑用于执行执行时间工具的内置控制的一种可能解决方案的主要内容,该工具旨在提取和积累有关受控软件块的时间度量的详细信息,并为开发人员随后生成报告。

检索运行时数据的方法


任何软件应用程序的功能都可以解释为抽象机器,它具有一组有限的唯一状态 {St},并在它们之间具有过渡{Tr}

在这种方法的框架中,应将应用程序中的任何执行流程都解释为其状态及其之间的转换的有序序列。 在这种情况下,执行时间成本的估算是通过将整个通过状态集上的时间度量求和而忽略从一个状态到另一状态的转换成本(可忽略的值)来执行的。

通过指定的控制点在应用程序执行时提取和累积数据是下面描述的内置控制工具解决的主要任务。

对于源代码中声明的每个断点,请放置
PROFILE_ENTRY C ++宏,记录其在应用程序执行期间的通过次数,以及时间度量-从检查点传递到程序层次结构的下一级(包括块,类方法,函数等)起,应用程序处于状态的总时间。如下图所示。

控制点的控制(初始注册和时间度量的计算)由在单个实例中创建的“ timeManager”对象执行。 每次通过控制点的事件都由对象'timeManager'记录并在第一次通过时将其记录为可观察到的'registerEntry'

在每次通过控制点时, 都会创建一个timerObject对象,以固定其创建时间。 当应用程序从软件层次结构的当前级别退出时,执行时间固定在检查点。 这时,对象的timerObject会自动销毁,并伴随其“生存期” T的计算。结果, timeManager增加了检查点通过的次数以及在其中花费的时间 对于所有设置的控制点,当应用程序终止时, 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()对,这是EREMEX开发的Delta Design软件产品的一部分,该产品是电子设备的计算机辅助设计系统。

以下是有关上述产品功能之一的时间指标的简短报告示例。


简要结论


所描述的工具可用于在其程序代码的各个部分中收集有关应用程序执行时间的数据,尤其是它们允许:

  • 根据应用程序中执行线程的时间指标收集和累积数据;
  • 对程序代码的执行时间进行估计以符合基本语言结构;
  • 通过打开和关闭应用程序执行流的相应部分上的内置控制工具来管理提取的数据量;
  • 开发并应用回归测试以监视应用程序时间指标的稳定性(并检测退化)。

总之,应该指出的是,在本出版物的范围之外,还有在多线程应用程序中使用所描述的内置控制工具的问题,并且没有以任何形式对通过时间度量获得的数据的准确性进行分析。 后者是由于以下事实:在实践中,当确定应用程序临时降级的原因时,与应用程序的软件组件之间的执行时间成本 相对分布 有关的数据主要相关 在这方面,关于获得的数据的准确性的问题逐渐淡出背景。

附录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/zh-CN468403/


All Articles