9.3.4 性能剖析 Profiling

熟悉 Go 语言的开发者一定了解 pprof。当我们需要对软件性能调试与分析时,可以通过 pprof 的 CPU profiling 或 Memory profiling 功能来分析函数的耗时和内存占用情况。在可观测性领域,性能剖析(Profiling)和 Go 语言中的 pprof 作用相同,都是通过对动态程序进行剖析,生成程序运行时的动态画像(也就是 Profiles 数据)。从而帮助我们了解程序使用各类资源的全貌,确定代码和性能瓶颈之间的关联。

Profiles 数据一般表示成火焰图、堆栈图或内存分析图等形式,是从“是什么”到“为什么”这一过程中至关重要的依据。例如,通过链路追踪发现延迟产生的位置,再借助 Profiles 生成的火焰图进一步定位到具体的代码行。2021 年国内某站崩溃,工程师们就是使用火焰图观察到到一处 Lua 代码存在异常,并定位到问题的源头[1]


图 9-20 Lua 级别的 CPU 火焰图

火焰图分析说明

  • 火焰图的 y 轴代调用栈,每一层都是一个函数调用,栈越深火焰越高,调用关系从上而下,顶部就是正在执行的函数,下方都是它的父函数。
  • x 轴代表抽样数,一个函数在 x 轴占据的宽度越宽,则说明它被抽样的次数越多,也就是说它的执行时间越长。

火焰图就是看顶层的哪个函数占据的宽度最大。只要有“平顶”,就表示该函数可能存在性能问题。火焰图颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。

可观测中的 Profiles 数据由多种不同的 Profiler 生成,常见的有:

  • CPU Profiler(CPU 分析器):用于分析哪些函数或方法在运行时消耗了最多的 CPU 时间。例如,通过 CPU Profiler,我们可以确定某个算法的优化是否减少了 CPU 使用率。
  • Heap Profiler(堆分析器):用于监测程序的内存使用情况,帮助发现内存泄漏或不必要的内存分配。例如,在 Java 应用中,Heap Profiler 可以帮助找到导致内存溢出的具体对象或数据结构。
  • GPU Profiler(GPU 分析器):用于分析图形处理单元(GPU)的利用情况,常用于游戏开发或图形密集型应用。
  • Mutex Profiler(互斥锁分析器):用于检测互斥锁的竞争情况,帮助优化多线程程序的并发性能。
  • IO Profiler(I/O 分析器):分析 I/O 操作的性能,如用来分析文件读写操作的延迟或网络请求的耗时,从而优化数据传输效率。
  • Language-specific Profiler:特定于语言的分析器,如 JVM Profiler,专门用于分析运行在 Java 虚拟机上的应用程序。

过去,由于这些分析器开销很大,工程师们通常在万不得已的情况下才临时使用。不过,随着编程语言层面的 Java Flight Recorder、Async Profiler,操作系统层面的 systemTap、eBPF 等的低开销剖析方法的普及,常态化运行的持续性能剖析(Continuous Profiling)辅助程序员精准的抓取一些偶发故障的现场快照,也变得愈加可行。


  1. 参见《2021.07.13 我们是这样崩的》https://www.bilibili.com/read/cv17521097/ ↩︎

总字数:898
Last Updated:
Contributors: isno