将找出其他程序的性能/属性的程序。

时间:2015-01-25 02:26:30

标签: c linux debugging performance-testing instrumentation

我需要编写一个程序来查找其他程序的性能/属性。

例如,让我们说,我想知道当我在linux中给出top命令时调用的函数是什么。我需要知道top命令中的哪个函数首先被调用,哪个函数负责打印输出。

或者可以用另一种方式来思考,我想知道在程序中调用的函数是什么,按顺序排列,以及它花在执行上的时间。

2 个答案:

答案 0 :(得分:1)

有两种流行的方法可以累积有关程序的分析信息。您可以使用专用的探查器,也可以通过调用clock手动设置代码。在这两种情况下,您都必须重新编译代码,但只有在第二种情况下,您必须修改源代码。

使用工具支持

如果您使用的是GNU工具链,那么您正在寻找Gprof。它与GCC结合在一起,完成了您所要求的目标。它分三步进行:

  1. 使用特殊选项-pg编译程序。这将导致GCC使用累积分析统计信息的特殊指令来检测二进制文件。
  2. 您在代表性输入集上运行程序。在程序退出时,分析统计信息将写入特殊文件以供以后分析。
  3. 您运行(在终端中)gprof main > profile.txt,用main替换您的可执行文件名。这会将人类可读的分析数据输出到文件profile.txt
  4. 该文件包含两个部分:“平面轮廓”和“调用图”。引用the manual

      

    平面配置文件显示程序执行每项功能所花费的总时间。 [...]

         

    这是小型程序的平面配置文件的一部分:

         
    Flat profile:
    
    Each sample counts as 0.01 seconds.
      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
     33.34      0.02     0.02     7208     0.00     0.00  open
     16.67      0.03     0.01      244     0.04     0.12  offtime
     16.67      0.04     0.01        8     1.25     1.25  memccpy
     16.67      0.05     0.01        7     1.43     1.43  write
     16.67      0.06     0.01                             mcount
      0.00      0.06     0.00      236     0.00     0.00  tzset
      0.00      0.06     0.00      192     0.00     0.00  tolower
      0.00      0.06     0.00       47     0.00     0.00  strlen
      0.00      0.06     0.00       45     0.00     0.00  strchr
      0.00      0.06     0.00        1     0.00    50.00  main
      0.00      0.06     0.00        1     0.00     0.00  memcpy
      0.00      0.06     0.00        1     0.00    10.11  print
      0.00      0.06     0.00        1     0.00     0.00  profil
      0.00      0.06     0.00        1     0.00    50.00  report
    ...
    
         

    首先通过减少在其中花费的运行时间,然后通过减少调用次数来排序函数,然后按名称按字母顺序排序。函数mcountprofil是剖析设备的一部分,并出现在每个平面轮廓中;他们的时间可以衡量分析产生的开销。

    再次引用the manual

      

    调用图显示每个函数及其子函数的花费时间。根据这些信息,您可以找到虽然它们本身可能没有花费太多时间的功能,但却调用了其他使用了异常时间的功能。

         

    以下是来自小型程序的示例调用。此调用来自与上一节中的平面配置文件示例相同的gprof运行。

         
    granularity: each sample hit covers 2 byte(s) for 20.00% of 0.05 seconds
    
    index % time    self  children    called     name
                                                     <spontaneous>
    [1]    100.0    0.00    0.05                 start [1]
                    0.00    0.05       1/1           main [2]
                    0.00    0.00       1/2           on_exit [28]
                    0.00    0.00       1/1           exit [59]
    -----------------------------------------------
                    0.00    0.05       1/1           start [1]
    [2]    100.0    0.00    0.05       1         main [2]
                    0.00    0.05       1/1           report [3]
    -----------------------------------------------
                    0.00    0.05       1/1           main [2]
    [3]    100.0    0.00    0.05       1         report [3]
                    0.00    0.03       8/8           timelocal [6]
                    0.00    0.01       1/1           print [9]
                    0.00    0.01       9/9           fgets [12]
                    0.00    0.00      12/34          strncmp <cycle 1> [40]
                    0.00    0.00       8/8           lookup [20]
                    0.00    0.00       1/1           fopen [21]
                    0.00    0.00       8/8           chewtime [24]
                    0.00    0.00       8/16          skipspace [44]
    -----------------------------------------------
    [4]     59.8    0.01        0.02       8+472     <cycle 2 as a whole> [4]
                    0.01        0.02     244+260         offtime <cycle 2> [7]
                    0.00        0.00     236+1           tzset <cycle 2> [26]
    -----------------------------------------------
    
         

    带有破折号的行将此表分成条目,每个函数对应一个条目。每个条目都有一行或多行。

         

    在每个条目中,主要行是以方括号中的索引号开头的行。该行的结尾说明了该条目的功能。条目中的前面几行描述了这个函数的调用者,下面的行描述了它的子例程(当我们谈到调用图时也称为子例程。)

         

    条目按函数及其子例程中花费的时间排序。

    我希望这可以给你一个想法,并建议你自己阅读手册,了解如何有效地使用Gprof。关于Gprof的最后一件好事是,您可以将收集的统计信息反馈给GCC,以便更好地优化代码。这称为profile guided optimization (PGO)

    手工完成

    如果你坚持不使用像Gprof这样的工具,你当然可以手工做,但我建议你尽可能远离这个。如果你可以免费使用轮子,为什么要重新发明一个瘪胎呢?当然,对于所有事情都有权衡,在某些情况下,您可能只想描述一些功能,手动方法可能是有保证的,甚至更优越。 (通常,它不会赢。)

    无论如何,您可以决定哪些功能对您有意义。例如,我们假设我们要分析要素foobarbaz。首先编号,最好是枚举。

    enum ProfiledFunctions {
      PROF_FOO,
      PROF_BAR,
      PROF_BAZ,
      PROF_LENGTH,  // dummy, counts the number of functions
    };
    

    然后创建一个全局数组来记录每个函数所花费的时间。

    #include <time.h>
    
    static clock_t profile_data[PROF_LENGTH];
    

    并掌握所有功能。

    int
    foo(int a, int b)
    {
    #if COLLECT_PROFILING_DATA
      clock_t t_begin, t_end;
      t_begin = clock();
    #endif
      // Do useful stuff with 'a' and 'b'...
    #if COLLECT_PROFILING_DATA
      t_end = clock();
      profile_data[PROF_FOO] += t_end - t_begin;
    #endif
      return 0;
    }
    

    当然,您也会对barbaz执行相同的操作。您可能希望使用一些预处理器魔法来减少键入冗余样板代码的数量。

    我已经使用条件包围了分析逻辑,因此我们可以根据需要编译它们(在编译器命令行上使用-DCOLLECT_PROFILING_DATA=1)。您要做的 last 事情是不止一次地添加/删除此样板逻辑。 (你要做的第二件事就是这样做甚至一次,这让我可以回顾像Gprof这样的工具。)

    最后,您可以更改main

    int
    main()
    {
    #if COLLECT_PROFILING_DATA
      clock_t t_begin, t_end;
      t_begin = clock();
    #endif
      // Do what your program is supposed to do...
    #if COLLECT_PROFILING_DATA
      t_end = clock();
      fprintf(stderr, "foo:  %ju\n", (uintmax_t) profile_data[PROF_FOO]);
      fprintf(stderr, "bar:  %ju\n", (uintmax_t) profile_data[PROF_BAR]);
      fprintf(stderr, "baz:  %ju\n", (uintmax_t) profile_data[PROF_BAZ]);
      fprintf(stderr, "main: %ju\n", (uintmax_t) (t_end - t_begin));
    #endif
      return 0;
    }
    

    我还打印了在main中花费的时间 - 也就是整个程序 - 所以你可以判断你选择分析的功能是否实际覆盖了大部分执行时间。在这个时间,我已经排除了打印统计信息本身的分析开销,因为这会产生误导。

    这种方法收集的统计数据将是累积的。如果foo调用bar(并且没有其他人调用bar),则foo报告的时间将包括bar所花费的所有时间加上其他任何{{1}做了。如果函数以递归方式调用自身,则会获得错误数据,而只留下调用foo或类似函数的函数。如果您的函数执行此类操作,您确实希望使用专用的分析器工具。

答案 1 :(得分:0)

如果您只对系统调用感兴趣,可以使用跟踪工具,如strace:

strace -tt -o strace.log -C top

将运行&#34; top&#34;并记录系统调用,包括文件&#34; strace.log&#34;的时间,包括最后的摘要,显示执行各种调用所花费的时间。但这当然不会捕获应用程序代码本身发生的事情 - 因为您需要像上一个响应中所描述的那样进行真正的分析。但是,对于正在运行的二进制文件中发生的事情的快速查看,跟踪程序可以是一个很好的工具。