差分火焰图,让你的代码优化验证事半功倍

2023-11-01 17:04:57 浏览数 (2)

在性能分析中,我们常常会用到如下所示的火焰图:

火焰图

一般来说,我们将这种火焰图称为on-cpu火焰图,可以用来记录CPU上运行的程序的占比情况。除此之外,还有多种其他种类的火焰图,如:

  • Memory 火焰图
  • off-cpu火焰图
  • 差分火焰图
  • CPI火焰图

本文我们将介绍差分火焰图。主要介绍以下的内容:

  • 为什么要有差分火焰图
  • 如何生成差分火焰图
  • 差分火焰图的形成原理
  • 开源项目pyroscope

为什么要有差分火焰图?

在性能分析和优化的过程中,我们经常使用使用火焰图;而当一轮优化完成过后,我们需要做回归验证来判断性能是否提升。除了尝试通过诸如延时、qps等指标来判断是否优化成功,我们也可以通过火焰图的热点变化来进行判断。

而当比较两个火焰图的时候,我猜你大概会这么做:

对比火焰图

打开两个标签,横向的比对两个火焰图的区别,这样麻烦且不够精确。因此,我们尝试引入差分火焰图:

差分火焰图-brendangregg.com

差分火焰图是两个火焰图A、B比较之后的结果,我们可以认为是B-A。往往采取红蓝配色,我们也可以称之为是红蓝对比火焰图,其中红色代表增长,蓝色代表减少。例如deflate_slow函数:

defalte_slow变化

这个函数是红色的,说明A火焰图相对于火焰图B,函数deflate_slow的调用变多了18.16%;而蓝色的部分则是相反的,表达A火焰图相较于B火焰图,某函数的调用变少了。

想象一下现在有一个火焰图,我们优化了其中某个调用占比1%的函数,如果采取对比方法,恐怕很难发现,而采取差分火焰图,则能够很快的发现哪里变化了以及变化了多少。

总结:「差分火焰图可以帮助我们快速的进行回归验证,比较两个火焰图的变化。」

如何生成差分火焰图

我们可以用如下的方式生成差分火焰图:

代码语言:javascript复制
# 第一次Profiling结果
perf record -ag -F 999 -- sleep 20
perf script > A.stacks
# 第二次Profiling结果
perf record -ag -F 999 -- sleep 20
perf script > B.stacks
# 下载FlameGraph仓库
git clone --depth 1 http://github.com/brendangregg/FlameGraph 
# 折叠A.stacks和B.stacks
cd FlameGraph 
./stackcollapse-perf.pl ../A.stacks > A.folded
./stackcollapse-perf.pl ../B.stacks > B.folded
# 基于折叠结果做差
./difffolded.pl A.folded B.folded > diff.folded
# 生成差分火焰图
./flamegraph.pl diff.folded > diff.svg

这里需要读者对火焰图的生成比较熟悉,如果不熟悉的读者可以先了解一下火焰图的生成过程。

差分火焰图的形成原理

在生成差分火焰图的过程中,和生成一般的火焰图不同的一步是我们调用了difffolded.pl对两个折叠后的堆栈文件进行了比对,并生成了比对后的堆栈文件。我们不妨尝试构造两个堆栈数据:

代码语言:javascript复制
# 堆栈数据1
[root@VM-16-2-centos ~]# cat Atest.folded 
YDService;foo 1000
YDService;other 1000
A;B;C 1000
A;B;D 400
# 堆栈数据2
[root@VM-16-2-centos ~]# cat Btest.folded 
YDService;foo 1000
A;B;C 400
A;B;D 1000

接着对这两个数据进行差分:

代码语言:javascript复制
[root@VM-16-2-centos ~]# ./FlameGraph/difffolded.pl Atest.folded Btest.folded > diff.folded

查看数据:

代码语言:javascript复制
[root@VM-16-2-centos ~]# cat diff.folded 
YDService;foo 1000 1000
A;B;D 400 1000
YDService;other 1000 0
A;B;C 1000 400

可以看到,数据格式变成了调用栈 调用次数1 调用次数2的形式。我们将数据生成火焰图看看:

差分火焰图

我们不妨生成一个B数据的火焰图看看:

B数据火焰图

可以看到除了配色,这两个火焰图的结构是完全一致的。我们可以得出一个结论:「差分火焰图以采样数据B为基准」。既然是这样的话,当涉及到回归的时候,我们应当将初始数据作为采样数据B,这样我们的比较基准就是初始数据,这样能够更好的进行比较。

我们再来看配色:

差分火焰图减少的含义

可以看到这里A->B->C这个调用栈的调用次数是减少了25%,那么这个25%是相对什么来说的呢?

我们不妨看原始数据,A->B->C在数据A中出现了1000次,数据B中出现了400次,减少调用了600次。而整体的调用次数是2400,所以这里的减少百分比是相较于所有的采样数据而言的。

看到这里不知道聪明的读者有没有发现一个问题,YDService->other这个调用对比消失了。这是因为我们前文说了差分火焰图是以采样数据B为基准的,如果某个调用栈在B中完全没有出现的话,那我们就无法比对前后的变化。如果想要初步解决这个问题,可以反转顺序来进行比较,也即将采样数据A作为基准,这样就能看到A中有的部分了。因此,在进行回归验证的时候,我们可以考虑进行两次反转的差分,这样更能帮助我们发现变化的地方。

pyroscope

pyroscope是一个开源项目,目前已被grafana收购。其主要做的是持续性的Profiling,能够让我们从时间的维度查看系统上的调用栈信息:

pyrocope

其提供了Diff的功能,可以帮助我们查看某两个时间段内的调用栈变化:

Diff view

如果对该项目有兴趣,可以自行尝试。

参考资料

  • differential-flame-graphs(https://www.brendangregg.com/blog/2014-11-09/differential-flame-graphs.html)
  • pyroscope(https://github.com/grafana/pyroscope)

0 人点赞