在上一部分 Git 内部原理 中我们已经对 Git 的内部基本原理比较熟悉,如 Git 的工作区,Git 的数据对象等。
这一部分,我们从实际开发的角度上出发,看如何对 Git 进行 debug,进行性能分析,进而找出可以优化改进的点,然后如何编写可用测试用例,验证我们的任何改进,最后将修改补丁提交到上游社区。
本文介绍Git源码的调试与性能分析方法,重点讲解如何使用gprof和火焰图工具定位性能瓶颈。以实际案例展示性能分析过程,包括编译参数设置、函数调用时间分析和调用图解读,帮助开发者深入理解Git内部执行机制,为后续的代码优化和贡献提供必要的技术基础。
在获取最新源码后,就可以编译了:
1 | $ git https://gitee.com/mirrors/git.git |
性能分析
我们需要借助一些工具进行性能分析。
gprof
gprof 是 GCC 工具自带的用于读取 profile 结果文件的工具,以作程序性能分析用。
使用时,需要加上 -pg 编译参数,编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。
首先修改 Git 的编译脚本文件:
1 | checking paths.h presence... yes |
修改 config.mak.in 之后,需要执行 ./configure,更新 config.mak.autogen 文件
1 | $ ./configure |
然后选择我们想要测试的某个 Git 命令:
1 | 这里的/path/bin-wrappers/git 就是刚生成的二进制可执行文件 |
之后会生成 gmon.out 文件
查看:
1 | $ gprof -b ./git gmon.out | less |
1 | Flat profile: |
第一列 %time 表示时间百分比,(不含其调用函数的执行时间)
第二列 cumulative seconds 表示累积秒数,表示所有次执行的时间总和(不含其调用函数的执行时间)
第三列 self seconds 表示此函数单词执行时间(不含其调用函数的执行时间)
第四列 calls 表示调用次数,表示此函数被调用了多少次
第五列 self s/call 表示每次调用此函数平均消耗的时间(单位 s)
第六列 total s/call 表示总的被调用时间(单位 s)
第七列 name 表示调用函数名
这次的测试结果表明,执行 git rev-list --objects HEAD 时,调用 lookup_object 花时间最多,占用总时间的 66%,其次是调用 tree_entry,耗时 0.10s,再是 decode_tree_entry,耗时 0.07s。
我们可以只关心某个函数,比如当我们对 look_object 函数进行某些修改后,可以只看优化前后这个函数的调用时间变化如何,其它不相关函数可不关心。
往下翻,可以查看调用图:
1 | Call graph |
这张调用图展示了三条调用记录,index 号分别是 [1], [2], [3]
没条记录 index 号所在的行,是当前被调用的函数。
每条记录的当前被调用函数之上的行,都是其直接父函数
每条记录的当前被调用函数之下的行,都是其直接子函数
但是这种调用图的缺点就在于调用关系不够明显。好在我们可以借助火焰图来更好的查看调用图。
flame graph
首先安装 perf:
1 | $ sudo apt install linux-tools-common linux-tools-$(uname -r) linux-cloud-tools-$(uname -r) |
如果是用 Windows 上的 WSL2,以上安装方式不奏效,原因是 WSL2 使用定制版的 Linux Kernel,
我们需要手动下载源码,进行编译,获取 perf 工具:
1 | $ sudo apt install build-essential flex bison libssl-dev libelf-dev |
perf 常用命令:
pref list: 查看 perf 支持的监控事件(event)
perf stat:查看程序运行过程中各种 event 的统计
perf record:记录更详细的信息,包括 IP, Stack 等,会生成 perf.data 文件
perf report:读取 perf.data 文件,并输出 profile 结果
perf script:读取 perf.data 文件,并输出 trace 结果
更多信息,可以查看 perf 帮助文档
然后安装 flame graph 库:
1 | $ git clone https://github.com/brendangregg/FlameGraph.git |
下载完成后,无需编译,可直接使用里面的可执行文件。
stackcollapse.pl: for DTrace stacksstackcollapse-perf.pl: for Linux perf_events “perf script” outputstackcollapse-pmc.pl: for FreeBSD pmcstat -G stacksstackcollapse-stap.pl: for SystemTap stacksstackcollapse-instruments.pl: for XCode Instrumentsstackcollapse-vtune.pl: for Intel VTune profilesstackcollapse-ljp.awk: for Lightweight Java Profilerstackcollapse-jstack.pl: for Java jstack(1) outputstackcollapse-gdb.pl: for gdb(1) stacksstackcollapse-go.pl: for Golang pprof stacksstackcollapse-vsprof.pl: for Microsoft Visual Studio profiles
生成 perf.data 文件:
1 | $ sudo perf record -g -F 100 'test script' |
-g指定输出数据中包含调用堆栈
-F指定采用频率
生成 svg 文件:
1 | $ sudo perf script -i perf.data | stackcollapse-perf.pl | flamegraph.pl > out.svg |
最终的输出文件打开就是火焰图。
测试
Git 使用了特殊的测试框架,它的测试输出是按照 TAP 格式(Test Anything Protocol)
在这种框架中,产生 TAP 输出的叫做 TAP 生产者,读取 TAP 输出的叫做 TAP 消费者。
Git 的所有测试都是 shell 脚本,放在目录 t/ 中,要运行测试很简单,执行 make 就行。
1 | $ cd t |
同时在 Linux 中自带 prove 工具,这个工具可以运行 TAP 测试,而且具有很大有用的选项。
因此 Git 的测试框架使用起来非常灵活,并由此诞生了这个框架:sharness
感兴趣的可以去了解一下。