目录

概述

所谓:工欲善其事,必先利其器。

本文总结一些本人在开发时使用过的一些开发、调试、性能分析的一些工具和方法,包括tmux、tldr、gdb、Sanitizer、perf、bcc、火焰图、strace、fio、vmstat等。本文不会对这些工具进行详细地介绍,而是简要介绍其功能,旨在让读者知道存在这些工具,需要实现某些目的的时候能想起这些工具。

使用这些工具,能实现包括但不限于以下目的:

  1. 更高效地进行开发。
  2. 多种方式调试程序的方法,如远程调试,调试运行中的程序,调试崩溃的程序。
  3. 发现C++程序的内存问题(内存泄露、非法访问)、并发(Data Races、死锁)问题。
  4. 分析程序性能瓶颈,如on-cpu瓶颈,off-cpu 瓶颈,IO瓶颈,及其可视化展示。
  5. 分析程序的TLB miss,Cache miss,Page Faults,Context Switch等情况并进行针对性的优化。
  6. 分析程序各部分的组成,获取程序的汇编以进行底层分析。
  7. 获取C++程序的函数调用图 Call Graph,帮助阅读源码。
  8. 监控计算机的运行状态(CPU、IO)及各类IO的性能。
  9. 其它。。。

开发工具

git

版本控制工具,不多说了。

tmux

Tmux 是一个终端复用器(terminal multiplexer),是我平时日常开发中一直开着的软件。他能够对终端进行管理,例如分屏等等。

在开发中有一个场景,就是我本地用ssh连接远程服务器,开了多个终端并且打开到不同的目录里。下班后本地计算机断网了,这些ssh连接就断开了,导致第二天要重新打开这些终端然后cd到对应的目录,经常这样做感觉非常麻烦。

而tmux可以将会话与窗口的"解绑",用tmux建立一个session之后,在该session内建立的多个窗口在断网后还可以保留(甚至可以设置重启后还能恢复),每次只要在本地ssh进服务器,然后重新进入到tmux的session,就能恢复到之前的工作环境,可以节省不少时间。

zsh

zsh是一个替代bash的shell,它的功能非常强大,支持很多插件,能提高开发效率,安装oh my zsh后还可以让终端变得好看。

我最常用的插件有:

  1. zsh-autosuggestions:自动输入建议。

    输入一部分命令就会提示之前输入过的命令。

  2. Z:对,这就是它的名字,输入z+dirname 就能快速cd到一个该dirname代表的文件夹。不需要全名,只需要输入部分名称就会智能识别出你最曾去的目录下。

man

进行开发的时候,经常需要查看文档,如系统调用的文档,那么就可以用man来查看,不同标号表示不同的类型:

  • 1 用户命令, 可由任何人启动的。
  • 2 系统调用, 即由内核提供的函数。
  • 3 例程, 即库函数,比如标准C库libc。
  • 4 设备, 即/dev目录下的特殊文件。
  • 5 文件格式描述, 例如/etc/passwd。
  • 6 游戏, 不用解释啦!
  • 7 杂项, 例如宏命令包、惯例等。
  • 8 系统管理员工具, 只能由root启动。
  • 9 其他( Linux 特定的), 用来存放内核例行程序的文档。
  • n 新文档, 可能要移到更适合的领域。
  • o 老文档, 可能会在一段期限内保留。
  • l 本地文档, 与本特定系统有关的。

tldr

tldr的意思是Too Long Didn’t Read的缩写,说的是man page太长了,不要看。tldr是也是一个文档工具,用于查看linux命令的使用方法。它比较简短,通常是给出了一些常用的例子,让人能够快速地使用,而不是查阅复杂的文档。

chatGPT

chatGPT刚推出的时候非常火爆,问他很多专业问题它都能给出解决方案。遇事不决问一问chatGPT,没准会有惊喜。我认为使用chatGPT对于启发思路来说是不错的。当然,有时候它的回答是它"杜撰"的,如要使用还需验证。

copilot

copilot是一个代码补全工具,由OpenAI和Github联合出品,是一个写代码神器。它的提示功能非常强大,输入部分代码或者是注释,他就能提示出很长的代码,节省大量的时间,非常适合写一些工具类、模板化的代码。

他像一个代码检索库,当你开发一个功能时,很可能类似的东西别人已经实现过了,copilot能够给出比较好的代码建议。当然,代码并不一定正确,还需要仔细甄别。

调试

gdb

gdb是一个功能强大的调试工具,全称叫做 The GNU Debugger。在LLVM平台有一个lldb,和gdb类似。

gdb的功能非常强大,可以调试不同状态下的程序,如:

gdb attach pid 可以调试正在运行的进程。

gdbserver可以远程调试。

gdb program coredump 可以调试崩溃后生成coredump的程序,分析其崩溃的原因从而找到bug。

core dump

core dump即核心转储文件。当操作系统配置了开启coredump功能后,程序发生异常崩溃退出时就会产生一个core dump。

core dump文件包含运行时的进程地址空间的内容以及有关进程状态的其他信息,通常用于调试程序。有时候程序的bug是隐秘而且运行很长时间才能出错,很难复现bug,用常规的调试方法很难调试,这时就可以利用core dump调试程序。

例如使用gdb调试:gdb program coredump。

valgrind

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析、检测线程错误的软件开发工具。

Valgrind 是运行在Linux上的多用途代码剖析和内存调试软件。主要包括Memcheck、Callgrind、Cachegrind 等工具,每个工具都能完成一项任务调试、检测或分析。可以检测内存泄露、线程违例和Cache 的使用等。

valgrind可以发现一系列非法使用内存问题, 如访问未初始化的内存、访问数组时越界、忘记释放动态内存等问题。

检查内存泄漏命令 valgrind –tool=memcheck –leak-check=full ./demo_cpp。编译时使用 -g 参数 (可以知道是哪行, 否则好像不好定位)。

Callgrind工具可以用于生成函数调用关系图,能够获取每个函数的调用次数。

1
2
3
4
5
6
valgrind --tool=callgrind ./valgrind_callgrind
python3 gprof2dot.py -f callgrind -n10 -s ../callgrind.out.2437614 > call_graph.dot
dot -Tpng call_graph.dot -o valgrind.png
环境 dot  gprof2dot)
    sudo apt install graphviz
    git clone https://github.com/jrfonseca/gprof2dot.git 

Sanitizer

Sanitizer已经集成在gcc或者clang以及golang里面,使用asan或者tsan并不需要额外的工具,只需在gcc或者g++里面增加编译选项,增加一个连接库即可。Sanitizer是基于运行时的检查.

Sanitizer有很多种,用于分析不同的问题:

  • AddressSanitizer (detects addressability issues) and LeakSanitizer (detects memory leaks 内存泄漏) 。
  • ThreadSanitizer (detects data races and deadlocks) for C++ and Go 。go语言的race就是使用 ThreadSanitizer 来实现的竟态检测; c和c++(gcc, g++)也可以使用这个工具来进行竟态检测。
  • MemorySanitizer (detects use of uninitialized memory)。

objdump

objdump能对二进制文件进行分析和反汇编。

例如能获取它每个段的大小和偏移。

参数 -d 可以将指令进行反汇编,进行深入底层的调试。

性能分析

perf

perf是我分析程序性能时最常使用的工具,它的功能非常丰富, 可以对程序的各个方面进行性能分析。用tldr工具查看perf就可以看到它的常用功能。

perf trace

perf trace能准确地显示每个系统调用占用的时间。包括阻塞off cpu的时间。

perf trace会给出每个线程的系统调用。

通过对线程系统调用的占用时间的分析,可以判断出其主要消耗在什么syscall上,从而进行针对性而优化。

例如对一个执行文件write+sync的线程进行trace分析,发现该线程主要时间在futex上,比fdatasync消耗的时间都要多,说明锁的冲突较多,锁成为了该线程的一个瓶颈。从而可以针对性地优化。

Linux还有一个工具叫做strace,也是对syscall进行监控和统计,不过我个人更喜欢使用perf trace。

call-graph

perf能够获取到程序的call graph,配个gprof2dot和dot工具能够展示为svg图。

1
2
perf --call-graph dwarf your-program program-arguments
perf script | gprof2dot.py --format=perf | dot -Tsvg > profile_graph.svg

这种call graph不仅包含函数调用关系,还包含每个函数的on-cpu执行时间,对程序的性能分析非常有帮助。

perf c2c

perf c2c的c指的是cache,可以对cpu cache进行监测。

借助perf c2c可以发现伪共享问题。

它的输出如https://github.com/joemario/perf-c2c-usage-files/blob/master/c2c_example_report.out所示:

TLB分析

perf能够监控各种events,只需要添加参数 -e event 即可。

例如:

1
perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses,L1-icache-load-misses ./mem

我们可以根据perf对这些情况的分析结果,进行程序调整。

Branch Misses分析

perf可以对branch misses进行分析。

为了优化分支预测,可以对不同的分支情况使用likely宏,然后用perf分析branch misses数量,从而对如何使用likely宏进行指导。

Page Faults分析

page faults除了可以用perf分析,还可以用ps分析,如:ps -o majflt,minflt -C program。

Page Faults分为majflt和minflt。majflt代表major fault,中文名叫大错误,minflt代表minor fault,中文名叫小错误。这两个数值表示一个进程自启动以来所发生的缺页中断的次数。 当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作:

  • 检查要访问的虚拟地址是否合法
  • 查找/分配一个物理页
  • 填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
  • 建立映射关系(虚拟地址到物理地址)
  • 重新执行发生缺页中断的那条指令
  • 如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt

火焰图

火焰图是非常重要的性能调优工具,它能够将perf record得到的信息可视化地展示。

为什么要用火焰图? 因为即使是我们自己写的程序, 我们也不清楚各个模块具体消耗的CPU的比例。而火焰图在通过perf采样后, 能够得到调用栈信息以及比例, 能够体现出程序的性能瓶颈。

常见的火焰图类型有 On-CPU,Off-CPU,还有 Memory,Hot/Cold,Differential 等等

不同类型火焰图适合优化的场景不同,比如 on-cpu 火焰图适合分析 cpu 占用高的问题函数,off-cpu 火焰图适合解决阻塞和锁抢占问题。

火焰图示例:

speedscope

speedscope是一个可视化工具,可以用来替代火焰图,他比火焰图的svg图更灵活,功能更丰富。例如可以按时间序列看,可以分线程看。可以将perf生成的数据导入到speedscope中。

bcc

BPF Compiler Collection (BCC)是基于eBPFLinux内核分析、跟踪、网络监控工具。它的工具非常多,能够分析系统的各个方面。如图:

我用他的offcputime工具获取过程序的off-cpu数据,用于分析程序的阻塞,从而找出瓶颈进行优化。

strace

strace是linux的syscall追踪工具,和前面介绍的perf trace功能类似。用于分析程序的系统调用情况。

fio

fio是一个IO测试工具,支持非常多的io类型。

通过fio,可以测出计算机在某种IO负载下的性能(压测)。根据这个性能结果去和我们的程序的IO(数据库)性能进行对比,如果和fio的结果差距较大,那么说明没有充分利用硬件资源,还有较大的提升空间。

值得一提的是,fio的作者Jens Axboe正是Linux内核异步IO框架io_uring的作者。尴尬的是我的mac(M1 Pro芯片)的ubuntu 20.04虚拟机的fio却无法测试io_uring。

time

time可以获取一个程序的三种时间。通常来说,用户时间+系统时间代表了进程所消耗的实际 CPU时间。不过,有时候想知道的恰恰是阻塞的时间。

  • 真实时间 - 从程序开始到结束流失掉的真实时间,包括其他进程的执行时间以及阻塞消耗的时间(例如等待 I/O或网络);
  • User - CPU 执行用户代码所花费的时间;
  • Sys - CPU 执行系统内核代码所花费的时间。
1
2
  time sleep 3
sleep 3  0.00s user 0.00s system 0% cpu 3.007 tota

iostat

iostat能监控计算机的磁盘和cpu信息,例如让它每秒输出一次:

vmstat

vmstat可以监控processes, memory, paging, block IO, traps, disks and CPU 活动等的信息。

例如:

iotop

iotop像top一样,不过它针对的是IO,可以获取进程的实时IO。

总结

好用的工具可以帮助我们高效地进行开发、调试,帮助我们快速找出程序的瓶颈并进行优化。

推荐一些比较好的资料:

  1. The Missing Semester of Your CS Education(课程)
  2. 《性能之巅》
  3. 《提高C++性能的编程技术》
  4. 《C++性能优化指南》
  5. 《调试九法》
  6. 《C/C++代码调试的艺术》