【问题标题】:Is GNU gprof buggy?GNU gprof 有问题吗?
【发布时间】:2014-08-12 16:42:04
【问题描述】:

我有一个 C 程序,它通过函数 pi_calcPiBlock 调用函数 pi_calcPiItem() 600000000 次。因此,为了分析我使用 GNU gprof 的函数所花费的时间。结果似乎是错误的,因为所有调用都归因于main()。此外,调用图没有任何意义:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls  Ts/call  Ts/call  name
 61.29      9.28     9.28                             pi_calcPiItem
 15.85     11.68     2.40                             pi_calcPiBlock
 11.96     13.49     1.81                             _mcount_private
  9.45     14.92     1.43                             __fentry__
  1.45     15.14     0.22                             pow
  0.00     15.14     0.00 600000000     0.00     0.00  main

                        Call graph


granularity: each sample hit covers 4 byte(s) for 0.07% of 15.14 seconds

index % time    self  children    called     name
                                                 <spontaneous>
[1]     61.3    9.28    0.00                 pi_calcPiItem [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     15.9    2.40    0.00                 pi_calcPiBlock [2]
                0.00    0.00 600000000/600000000     main [6]
-----------------------------------------------
                                                 <spontaneous>
[3]     12.0    1.81    0.00                 _mcount_private [3]
-----------------------------------------------
                                                 <spontaneous>
[4]      9.4    1.43    0.00                 __fentry__ [4]
-----------------------------------------------
                                                 <spontaneous>
[5]      1.5    0.22    0.00                 pow [5]
-----------------------------------------------
                                   6             main [6]
                0.00    0.00 600000000/600000000     pi_calcPiBlock [2]
[6]      0.0    0.00    0.00 600000000+6       main [6]
                                   6             main [6]
-----------------------------------------------

这是一个错误还是我必须以某种方式配置程序?

&lt;spontaneous&gt; 是什么意思?

编辑(为您提供更多见解)

代码都是关于计算圆周率的:

#define PI_BLOCKSIZE (100000000)
#define PI_BLOCKCOUNT (6)
#define PI_THRESHOLD (PI_BLOCKSIZE * PI_BLOCKCOUNT)

int32_t main(int32_t argc, char* argv[]) {
  double result;

  for ( int32_t i = 0; i < PI_THRESHOLD; i += PI_BLOCKSIZE ) {
    pi_calcPiBlock(&result, i, i + PI_BLOCKSIZE);
  }

  printf("pi = %f\n",result);
  return 0;
}

static void pi_calcPiBlock(double* result, int32_t start, int32_t end) {
  double piItem;

  for ( int32_t i = start; i < end; ++i ) {
    pi_calcPiItem(&piItem, i);
    *result += piItem;
  }  
}    

static void pi_calcPiItem(double* piItem, int32_t index) {
  *piItem = 4.0 * (pow(-1.0,index) / (2.0 * index + 1.0));
}

这就是我得到结果的方式(在 Cygwin 的帮助下在 Windows 上执行):

> gcc -std=c99 -o pi *.c -pg -fno-inline-small-functions
> ./pi.exe
> gprof.exe pi.exe

【问题讨论】:

  • 代码?您使用什么命令行来查看结果?
  • @CDahn 我刚刚添加了您要的内容。
  • 函数内联是标准的 C 编译器优化。您无法获取不再存在的函数的函数调用计数。
  • @HansPassant 因此我只使用了-fno-inline-small-functions 参数来避免内联(因此,编辑了上面的描述)。 但是:gprof 的输出没有任何变化。
  • 使用 -S 再次检查您的假设。确实会有一个点,修补优化器设置开始产生无用的配置文件结果。您可以通过禁用内联来克服它。

标签: c profiling profiler gprof


【解决方案1】:

试试:

  1. 使用noinlinenoclone函数属性代替-fno-inline-small-functions
    • 通过反汇编 main 我可以看到 -fno-inline-small-functions 不会停止内联
  2. 静态链接您的程序 (-static)
  3. 您还应该在main 中将result 初始化为0.0

这在 Linux、x86-64 上对我有用:

#include <stdio.h>
#include <stdint.h>
#include <math.h>

#define PI_BLOCKSIZE (100000000)
#define PI_BLOCKCOUNT (6)
#define PI_THRESHOLD (PI_BLOCKSIZE * PI_BLOCKCOUNT)

static void pi_calcPiItem(double* piItem, int32_t index);
static void pi_calcPiBlock(double* result, int32_t start, int32_t end);

int32_t main(int32_t argc, char* argv[]) {
  double result;

  result = 0.0;
  for ( int32_t i = 0; i < PI_THRESHOLD; i += PI_BLOCKSIZE ) {
    pi_calcPiBlock(&result, i, i + PI_BLOCKSIZE);
  }

  printf("pi = %f\n",result);
  return 0;
}

__attribute__((noinline, noclone))
static void pi_calcPiBlock(double* result, int32_t start, int32_t end) {
  double piItem;

  for ( int32_t i = start; i < end; ++i ) {
    pi_calcPiItem(&piItem, i);
    *result += piItem;
  }  
}    

__attribute__((noinline, noclone))
static void pi_calcPiItem(double* piItem, int32_t index) {
  *piItem = 4.0 * (pow(-1.0,index) / (2.0 * index + 1.0));
}

构建代码

$ cc pi.c -o pi -Os -Wall -g3 -I. -std=c99 -pg -static -lm

输出

$ ./pi && gprof ./pi
pi = 3.141593
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ns/call  ns/call  name    
 85.61     22.55    22.55                             __ieee754_pow_sse2
  4.75     23.80     1.25                             pow
  4.14     24.89     1.09 600000000     1.82     1.82  pi_calcPiItem
  2.54     25.56     0.67                             __exp1
  0.91     25.80     0.24                             pi_calcPiBlock
  0.53     25.94     0.14                             matherr
  0.47     26.07     0.13                             __lseek_nocancel
  0.38     26.17     0.10                             frame_dummy
  0.34     26.26     0.09                             __ieee754_exp_sse2
  0.32     26.34     0.09                             __profile_frequency
  0.00     26.34     0.00        1     0.00     0.00  main


             Call graph (explanation follows)


granularity: each sample hit covers 2 byte(s) for 0.04% of 26.34 seconds

index % time    self  children    called     name
                                                 <spontaneous>
[1]     85.6   22.55    0.00                 __ieee754_pow_sse2 [1]
-----------------------------------------------
                                                 <spontaneous>
[2]      5.0    0.24    1.09                 pi_calcPiBlock [2]
                1.09    0.00 600000000/600000000     pi_calcPiItem [4]
-----------------------------------------------
                                                 <spontaneous>
[3]      4.7    1.25    0.00                 pow [3]
-----------------------------------------------
                1.09    0.00 600000000/600000000     pi_calcPiBlock [2]
[4]      4.1    1.09    0.00 600000000         pi_calcPiItem [4]
-----------------------------------------------
                                                 <spontaneous>
[5]      2.5    0.67    0.00                 __exp1 [5]
-----------------------------------------------
                                                 <spontaneous>
[6]      0.5    0.14    0.00                 matherr [6]
-----------------------------------------------
                                                 <spontaneous>
[7]      0.5    0.13    0.00                 __lseek_nocancel [7]
-----------------------------------------------
                                                 <spontaneous>
[8]      0.4    0.10    0.00                 frame_dummy [8]
-----------------------------------------------
                                                 <spontaneous>
[9]      0.3    0.09    0.00                 __ieee754_exp_sse2 [9]
-----------------------------------------------
                                                 <spontaneous>
[10]     0.3    0.09    0.00                 __profile_frequency [10]
-----------------------------------------------
                0.00    0.00       1/1           __libc_start_main [827]
[11]     0.0    0.00    0.00       1         main [11]
-----------------------------------------------

评论

正如预期的那样,pow() 是瓶颈。当pi 正在运行时,perf top(基于采样的系统分析器)也显示__ieee754_pow_sse2 占用了 60% 以上的 CPU。按照@Mike Dunlavey 的建议,将pow(-1.0,index) 更改为((i &amp; 1) ? -1.0 : 1.0) 可使代码快约4 倍。

【讨论】:

  • 当然,pow(-1.0, i) 可以简化为((i &amp; 1) ? -1.0 : 1.0)。应该让它更快。
  • 如果您对速度感兴趣并且不太担心有效位数的准确性,那么:355 / 113 产生的可用结果为:3.14159292,我在许多程序中使用过,其中小错误不重要。
  • 或者...您可以只使用 math.h[1] 中的 M_PI 常量,包含与“double”类型匹配的精度。如果您要定义自己的常数,为什么要使其不如已经存在的常数精确?我认为这个程序的重点是计算 pi,而不是使用 pi。 [1]pubs.opengroup.org/onlinepubs/007908799/xsh/math.h.html
【解决方案2】:

在“man gprof”页面中,这里是“自发”的解释:

自己没有被描述的父母将有时间 他们异形的孩子传播给他们,但他们似乎是 在调用图列表中自发调用,并且不会有 他们的时间进一步传播。同样,信号捕捉器,甚至 虽然是异形的,但似乎是自发的(尽管对于更多 不明原因)。信号捕捉器的任何异形子代都应该 有他们的时间正确传播,除非信号捕捉器 在分析例程执行期间调用,在这种情况下 一切都丢失了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-26
    • 2013-07-16
    • 1970-01-01
    • 2011-04-05
    • 1970-01-01
    • 1970-01-01
    • 2016-02-07
    • 1970-01-01
    相关资源
    最近更新 更多