调试与性能分析

2022-08-03
8 min read

调试

我常用的调试工具是 GDB (g++ -g)和二分查错法,先删除一半代码,看是否有问题,如果没有问题,那问题就在另一半代码中 :)

运行时

运行时(runtime debug)调试在一些场景下比较重要,比如调试阻塞的程序。运行时调试的工具有 strace、gdb、pstack、gstack、eu-stack、etraceegypt 等。如果有 root 权限,可以直接查看 PID 对应的栈文件,例如:cat /proc/pid/stack

strace

strace 用于跟踪进程的系统调用。父子进程场景下,直接使用 strace 跟踪父进程可能造成 strace 阻塞。此时需要获取子进程 ID 然后再使用 strace 命令

ps -efL | grep client # 下面 24813 是父进程 ID,24821、24822 是子进程 ID
# root 24813 1 24813 0 7 11:07 pts/1 00:00:00 ./client -i /home/c...
# root 24813 1 24821 0 7 11:07 pts/1 00:00:02 ./client -i /home/co...
# root 24813 1 24822 0 7 11:07 pts/1 00:00:02 ./client -i /ho...

strace -s 99 -ffp pid

GDB

gdb 可直接依附到运行中的进程gdb -batch -ex bt -p pid 。如果可执行文件中没有 debug info,gdb 可能不会输出有效的 stack 信息

gdb -p PID # 直接依附到对应进程

# 先进入 gdb 然后再依附进程
gdb
attach pid
bt

# gdb 内查看一些信息
thread # 查看当前线程 ID
info threads # 查看所有线程
thread tid # GDB 切换到线程 ID 为 tid 的线程

一些场景下我们可能不能打 Debug 模式(-g)的可执行去 Debug,此时可以考虑为 g++ 提供这两个 flag-fno-omit-frame-pointer -fno-optimize-sibling-calls

GDB debug 选项可以参考这里:https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html。使用 -ggdb 可以获得比 -g 更详细的调试信息

pstack&gstack

pstack / gstack - Print a stack trace of running processes

pstack/gstack 依赖 gdb,gdb 无法实现的功能,pstack/gstack 一般也不支持。使用示例如下

pgrep nginx
# 42926
sudo pstack 42926
# 42926: nginx: master process /usr/sbin/nginx
# (No symbols found)
# 0x7fb1d7da645c: ???? (0, 0, 0, 0, 0, 0) + ffffaa7085125e60

eu-stack

eu-stack, Print a stack for each thread in a process or core file

eu-stack 是 elfutils 中的工具,执行速度比基于 gdb 的 stack 工具要快。用法示例如下

$ eu-stack -p 2209
PID 2209 - process
TID 2209:
#0  0x00007f53476b667b __poll
#1  0x00007f5348f98e99 g_main_context_iterate.isra.23
#2  0x00007f5348f99232 g_main_loop_run
#...
TID 2223:
#0  0x00007f53476b667b __poll
#1  0x00007f5348f98e99 g_main_context_iterate.isra.23
#...

perf

perf 的一些细节可以参考下文性能分析章节。perf 开启 --call-graph 后可以查看函数调用关系,没有其他工具时可以先考虑使用 perf 或 valgrind

内存/线程问题

内存问题不太好调试,特别是一些随机崩溃的场景,此时需要一些工具来帮忙检查程序运行时的问题

ASAN/TSAN

ASAN 和 TSAN 是 Clang 与 GCC 都支持的内存(Address)和线程(Threads)调试工具,可以编译时开启

细节可以参考 Github:https://github.com/google/sanitizers

Valgrind

下面的 Valgrind 也支持内存和线程的检查,这里不重复介绍

性能分析

Brendan Gregg 在性能分析领域非常出名,他个人主页有很多性能分析资料,是非常好的参考和学习材料。gprof 虽然是 gcc 自带的性能分析工具,但其不一定合适,其他工具也可以,例如使用 perf 做程序热点分析

性能提升有不同的方法,例如

  1. 应用级别:调整编译选项、调用高性能库、去掉全局变量等
  2. 算法级别:缓存优化、查表法(不重复计算)等
  3. 函数级别:减少参数个数、只使用入参和局部变量、内联小函数等
  4. 循环级别:减少循环次数、提升循环内部的并行性(循环展开)、循环合并/拆分等
  5. 语句级别:减少内存读写、结构体对齐、分支优化(尽量减少分支)、合并条件判断等
    1. 一些分支条件是包含多个比较操作的复杂表达式,编译器通常将它们编译成嵌套的多个比语句代码
  6. 指令级别:减少数据依赖、注意处理器的多发射能力、优化乘除与模运算等

流程

优化程序性能一般需要结合下面多个工具,例如使用 gprof/valgrind 找到整个程序的热点函数与调用关系,看是优化底层函数还是上层函数;确定全局热点后可以找不同模块的热点函数,此时可以考虑使用 perf

graph LR
	A[Define the</br> problem]
	B[Determin the source </br>of problem]
	C{avoided?}
	D[Design</br>Experiment]
	E[Check<br/>upstream]
	F[Algs<br/>impr]
	G[Architectural<br/>tuning]
	H[test]
	I[Regression]
	
	A-->B
	B-->C
	C-->D
	C-->H
	D-->E
	E-->F
	F-->G
	G-->H
	H-->I
	I-->A
  1. 确定问题来源。网络带宽、磁盘带宽、代码瓶颈等。这个过程需要使用 profiling 工具进行定位(frame time and frame rate )
  2. 确定瓶颈是否可以避免。比如使用更好的网络、将机械硬盘替换为 SSD 等
  3. 设计实验,方便验证
  4. 与上下游进行沟通,尽可能多的搜集解决方案。开源工具是否有调优参数?当前模块是否有新的开发分支?
  5. 是否有更优的算法解决当前问题?
  6. 代码结构与细节调整以更好的适配硬件。例如提升分支预测、数据局部性、SIMD 指令等
  7. 测试&回归。重复进行上面的流程

工具

下面介绍一些常见的工具,其他工具例如 ftrace、sysprof 等工具可以参考 《Power and Performance》 / 《性能之巅》 等资料

Phoronix

Phoronix 是跨平台的基准测试工具

gprof2dot

This is a Python script to convert the output from many profilers into a dot graph.

gprof2dot 是很多性能分析工具的可视化脚本,例如 perf、valgrind、vtune、gprof 等。gprof2dot 的细节与输出控制命令可以参考:https://github.com/jrfonseca/gprof2dot#documentation

perf

Because PERF is a statistical sampling process, you need to collect enough individual samples to produce a statistically meaningful result and to reduce measurement error. Another way to increase the number of samples is to increase the resolution(perf record --freq=8000 ./xyz) at which samples are taken

apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`

perf 是 Linux 内核 2.6.31 之后支持的、用户空间的、基于命令行的性能分析工具。perf 对程序性能的分析基于事件事件分为硬件和软件两大模块,不同平台下硬件事件的类型是不同的,perf 支持的事件可以通过 perf list 进行展示

perf 官方教程 给出了一些实例和说明。通过配置 perf_event_paranoid 可以控制非 root 用户 perf 的使用权限,如果设置的值小于 0,则普通用户可以使用 perf 的绝大部分功能

sudo sysctl -w kernel.perf_event_paranoid=-1

通过指定子命令,perf 可以对程序做多个维度的性能分析,例如缓存行(c2c)、锁(lock)、内存访问(mem)等

常用子命令

如果需要详细的热点信息,编译时可以使用 -ggdb 添加更为详细的 Debug 信息,细节请参考:使用 perf 做程序热点分析 / brendangregg perf Examples / 。perf 相关命令可以参考 perf manpage

perf # show all sub-commands
perf stat -h # help for sub-cmd
perf list -h # List all symbolic event types

# stat, gather performance counter statistics
perf stat dd if=/dev/zero of=/dev/null count=100000
perf stat -e cpu-clock,faults dd if=/dev/zero of=/dev/null count=100000

# record, collect profiles on per-thread, per-process and per-cpu basis
# using -g (callgraph) to show child and self consume
# 启用 --call-graph 可能生成非常大的 data 文件
perf record -g -e cpu-clock,faults -F 99 --call-graph dwarf \
					  dd if=/dev/zero of=/dev/null count=100000

# report, Read and display the profile
perf report --tui/gtk/stdio
perf report --stdio --sort comm,dso
# restricts output to the specified DSO
perf report --stdio --dsos=abc,libc-2.17.so 

# annotate, restrict output to specific DSO and functions (symbol)
perf annotate --stdio --dsos=abc --symbol=xyz --no-source

# 可视化
perf script | c++filt | python ./gprof2dot.py -n20 -f perf | dot -Tpng -o perf.png
hotspot&FlameGraph

hotspot / Flame Graph 是 perf 的辅助工具,可以提供可视化结果,细节可以参考:CPU Flame Graphs

perf record -a -F 99 -g --call-graph dwarf /path/to/cmd args
perf script | /path/to/FlameGraph/stackcollapse-perf.pl > out.perf-folded
/path/to/FlameGraph/flamegraph.pl out.perf-folded > perf.svg
firefox perf.svg

gprof

gprof (-pg) 是 GNU 编译器工具包提供的分析工具,gprof 能够给出函数调用关系、调用次数、执行时间等信息

使用方法

给 GNU Tools 传递 PG 选项,编译器和连接器都需要加上-pg 选项。启动程序,待程序执行完成之后(可手动停止)将自动生成 gmon.out 文件,当前文件包含了函数调用信息。使用 cmake 时如果需要开启 gprof,需要添加如下命令:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg") 

TARGET_LINK_LIBRARIES (app_name /path/to/libprofiler.so)

执行可执行文件后将生成 gmon.out 文件,使用 gprof 命令可以分析并生成文本形式的调用分析结果

g++ -g -pg ... # run and generate gmon.out file

gprof /path/to/myprog /path/to/gmon.out > t.txt

利用 gprof2dot 与 dot 命令可以可视化 prof 命令分析后的文本调用分析结果

# ubuntu
sudo apt-get install python graphviz
sudo pip install gprof2dot

gprof2dot -n 7 -s ./t.txt | dot -Tpng -o output_dg.png

如果 gprof 没法正常给出 callgraph,可以考虑使用 valgrind --tool=callgrind myapp args

valgrind

valgrind 原本用来査找程序的内存泄露问题,现在也支持剖分程序。 比 gprof 方便的是它不用重新编译程序,可直接分析可执行文件, 当然编译时增加 -g、-fno-inline 选项能够提供更多的信息。但 valgrind 很慢,因为 valgrind 相当于一个虚拟机,程序的每条指令都会被 valgrind 进行解释然后由真实 CPU 执行…

valgrind 由多个组件组成,例如用于内存检测的 Memcheck、用于缓存行检查的 Cachegrind、可以绘制函数调用关系的 Callgrind、Massif(堆)、Helgrind&DRD(Thread)等

官方提供了快速入门user manual 等资料,细节可参考官网

valgrind core

Valgrind simulates every single instruction your program executes, including the C library, graphical libraries, and so on

Your program is then run on a synthetic CPU provided by the Valgrind core. As new code is executed for the first time, the core hands the code to the selected tool. The tool adds its own instrumentation code to this and hands the result back to the core, which coordinates the continued execution of this instrumented code

The amount of instrumentation code added varies widely between tools. At one end of the scale, Memcheck adds code to check every memory access and every value computed, making it run 10-50 times slower than natively. At the other end of the spectrum, the minimal tool, called Nulgrind, adds no instrumentation at all and causes in total “only” about a 4 times slowdown

常用命令
# Memcheck is the default tool
valgrind --leak-check=yes myprog arg1 arg2
valgrind --tool=memcheck --leak-check=yes myprog arg1 arg2

# Callgrind & 可视化
valgrind --tool=callgrind myapp args
python ./gprof2dot.py -f callgrind -n1 -s ./callgrind.out.54174 \
       | dot -Tpng -o valgrind.png
高级用法

valgrind 可以结合 GDB 进行使用,因为使用 valgrind 进行调试的程序执行在 valgrind 虚拟出来的 CPU,所以 valgrind 结合 GDB 需要使用一些额外的命令,这部分内容请参考官网。其他高级功能,例如:function wrapping 等

nvprof

nvprof 是 NVIDIA 开发的、用于分析运行在其 GPU 上的 CUDA 程序性能的工具

vampir

vampir 的含义是 “吸血鬼”,但是 vampir 程序的目的却是消除并行程序中的性 能 “吸血鬼”。 vampirTrace 是一个基于命令行的并行程序剖分工具,而 vampir 能够图形化地显示 vampirTrace 的结果。vampirTrace 支持 MPI、OpenMP 和 pthread,另外也支持 nvidia 的基于 GPU 的并行环境 CUDA,它能够在 Linux、Windows 上运行

Intel VTune

Intel VTune 工具能够分析程序的性能瓶颈、 函数的调用关系、 函数和语句的执行时间、 每条高级语言代码对应的汇编代码, 以及并发性和锁的消耗。VTune 能够分析程序在特定硬件架构上的带宽、 端口使用、 处理器前端和后端的使用情况