我已经设置了 CPUPROFILE 环境变量和链接的 -lprofiler。为什么 gperftools 不启动探查器?

I've set the CPUPROFILE environment variable and linked -lprofiler. Why is gperftools not starting the profiler?

本文关键字:gperftools 为什么 启动 探查 -lprofiler 设置 CPUPROFILE 链接 环境变量      更新时间:2023-10-16

根据 gperftools 文档,可以使用以下任一方法启动探查器:

  1. CPUPROFILE环境变量设置为配置文件信息将保存到的文件名
  2. 执行上述操作,并设置CPUPROFILESIGNAL并发送适当的信号以开始或停止采样。
  3. 直接从代码调用ProfilerStart(filename)ProfileStop()

所有这三种方法都需要链接libprofiler.so

当我

尝试这样做时,第三种方法有效,但是当我只设置CPUPROFILE时,没有生成分析信息。

不起作用:

$ cat foo.c
#include <stdio.h>
int main(void) {
    printf("Hello, world!n");
}
$ gcc foo.c -std=c99 -lprofiler -g && CPUPROFILE=foo.prof ./a.out
Hello, world!
$ ls foo.prof
ls: cannot access foo.prof: No such file or directory

确实有效:

$ cat bar.c
#include <stdio.h>
#include <gperftools/profiler.h>
int main(void) {
    ProfilerStart("bogus_filename");
    printf("Hello, world!n");
    ProfilerStop();
}
$ gcc -std=c99 bar.c -lprofiler -g && CPUPROFILE=foo.prof ./a.out 
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64
$ ls foo.prof
foo.prof
$ ls bogus_filename
ls: cannot access bogus_filename: No such file or directory
$ ./a.out
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64
$ ls bogus_filename
bogus_filename

请注意,正在读取CPUPROFILE,因为它的值将覆盖传递给ProfileStart()的文件名(如果已设置(。

解决此问题所需的所有信息都分散在Stack Overflow中,但是将其放在一个地方会很有用,所以现在它是。 我已经包含了我在解决这个问题时发现有用的答案的参考,以防有人正在寻找更多信息。

在 gperftools 中,CpuProfiler的构造函数检查CPUPROFILE并调用ProfilerStart(getenv("CPUPROFILE"))是否设置(加上或减去一些其他条件(。 CpuProfilerprofiler.cc 中声明,以确保调用该函数。[1] 当然,这只有在libprofiler.so是链接的情况下才会发生。

以下代码揭示了该问题:

$ cat baz.c 
#include <stdlib.h>
#include <stdio.h>
#include <gperftools/profiler.h>
int main(void) {
    volatile int i = 0;
    if (i) ProfilerStop();
    printf("Hello, world!n");
    return 0;
}
$ gcc -std=c99 baz.c -lprofiler -g && CPUPROFILE=foo.prof ./a.out 
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64

ProfileStop()实际上永远无法调用,但由于i是易失性的,编译器无法对其进行优化,因此链接器需要引入libprofiler进行定义。 默认情况下,-lprofiler只引入程序中实际出现的符号,在原始情况下没有这些符号,因此它根本没有链接库,并且CpuProfiler()从未被调用。

解决方法是在链接libprofiler.so之前将 --no-as-needed 标志传递给ld。 [2] 这会导致它链接库,无论它是否在程序中使用它中的任何内容(ld 手册页似乎建议这应该是默认行为,但它对我来说不是这样工作的(。然后传递--as-needed标志,以便在我们加载所需内容后将其关闭。 (顺便说一句,--whole-archive似乎是静态库的等效选项[3](

使性能分析适用于原始文件的编译命令:

$ gcc -std=c99 foo.c -Wl,--no-as-needed,-lprofiler,--as-needed -g && CPUPROFILE=foo.prof ./a.out 
Hello, world!
PROFILE: interrupts/evictions/bytes = 0/0/64