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

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

根据 gperftools documentation,可以使用以下任何一种方法启动分析器:

  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"))(加上或减去一些其他条件)。在profiler.cc中声明了一个CpuProfiler来确保函数被调用。 [1] 当然,这只有在 libprofiler.so 被 link 编辑时才会发生。

以下代码揭示了这个问题:

$ 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 是易变的,编译器无法优化它,因此 linker 需要引入 libprofiler定义。默认情况下,-lprofiler 只引入实际出现在程序中的符号,在原来的情况下是其中的 none,所以它根本没有 link 库, CpuProfiler() 从未接到电话。

修复方法是在 linking libprofiler.so 之前将 --no-as-needed 标志传递给 ld[2] This causes it to link the library whether or not it anything from it is used in the program (the ld man page seems to suggest that this should be the default behavior, but it wasn't working that way for me). The --as-needed flag is then passed to turn it back off once we've loaded what we need. (As an aside, --whole-archive appears to be the equivalent option for static libraries [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