git 的 delta 链是怎么形成的?window 和 depth 各自控制什么?这篇用同一个文件的 40 个版本,一次一个参数地重打包,看 pack 大小、最长链和读取耗时,最后看 gc --aggressive 到底传了什么参数。
40 个版本的原始字节合计 869,708;最新版 22,779 字节 window=0 depth=0(完全不做 delta):pack 560,627 字节
--window 控制为每个对象同时考虑多少个候选基础(对象按类型、文件名、大小排序,相近的挨在一起);--depth 限制 delta 链的最大长度。窗口大找到更好基础的机会多,深度大可以把链接得更长。
window=1 pack 102,498 字节 最长链 1 完整存放 81 objects window=2 pack 28,623 字节 最长链 39 完整存放 81 objects window=5 pack 28,623 字节 最长链 39 完整存放 81 objects window=10 pack 28,623 字节 最长链 39 完整存放 81 objects window=50 pack 28,623 字节 最长链 39 完整存放 81 objects
40 个版本是同一个文件的线性历史,每个版本的最佳基础就是相邻的版本,所以窗口 2 就已经把大小压到最小。窗口大小真正起作用的是大量互相有些相似的对象的场合。
depth=0 pack 560,627 字节 最长链 0 完整存放 120 objects depth=1 pack 102,498 字节 最长链 1 完整存放 81 objects depth=2 pack 49,400 字节 最长链 2 完整存放 81 objects depth=5 pack 35,328 字节 最长链 5 完整存放 81 objects depth=10 pack 32,357 字节 最长链 10 完整存放 81 objects depth=50 pack 28,623 字节 最长链 39 完整存放 81 objects
depth 从 0 到 1 就缩小了 82%,从 10 到 50 只再省 12%。越深越小,但边际收益快速下降。链太深会让读取要沿链多次还原,所以 git 默认的最大深度是 50。
depth=0 读完 40 个版本: 11.8 ms depth=1 读完 40 个版本: 10.3 ms depth=5 读完 40 个版本: 10.2 ms depth=50 读完 40 个版本: 11.2 ms
理论上链越深读越慢,这里 40 个 22 KB 的版本在一个进程里读完,depth=0 到 depth=50 的耗时都在 10~12 ms 之间,看不出区别;大文件和更长的链没有测。
$ GIT_TRACE=1 git gc -q 2>&1 | grep "built-in: git repack" | sed "s/.*built-in: //" git repack -d -l -q --cruft --cruft-expiration=2.weeks.ago $ GIT_TRACE=1 git gc --aggressive -q 2>&1 | grep "built-in: git repack" | sed "s/.*built-in: //" git repack -d -l -f --depth=50 --window=250 -q --cruft --cruft-expiration=2.weeks.ago
普通 git gc 的 repack 不加 -f,会复用已有的 delta;--aggressive 加 -f --depth=50 --window=250,把所有 delta 重新计算,更小也更慢。