bigywater.tech
Git · 18

delta 链:window 与 depth 对 pack 大小的影响

git 的 delta 链是怎么形成的?window 和 depth 各自控制什么?这篇用同一个文件的 40 个版本,一次一个参数地重打包,看 pack 大小、最长链和读取耗时,最后看 gc --aggressive 到底传了什么参数。

560,627 → 28,623
40 个版本的 pack 大小:不做 delta 与 depth=50、window≥2 的对比(字节)
window=2
一个文件的线性历史里,窗口 2 就已达到本例的最小值
-f --depth=50 --window=250
git gc --aggressive 传给 repack 的参数

window 与 depth

实测 · 本机执行输出
  40 个版本的原始字节合计 869,708;最新版 22,779 字节
  window=0 depth=0(完全不做 delta):pack 560,627 字节

--window 控制为每个对象同时考虑多少个候选基础(对象按类型、文件名、大小排序,相近的挨在一起);--depth 限制 delta 链的最大长度。窗口大找到更好基础的机会多,深度大可以把链接得更长。

这组数据的 window

实测 · 本机执行输出
  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 的收益递减

实测 · 本机执行输出
  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 之间,看不出区别;大文件和更长的链没有测。

gc 与 aggressive

实测 · 本机执行输出
  $ 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 重新计算,更小也更慢。

分步演示

window 与 depth
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。repack 实验固定 pack.threads=1(不固定时,git 会把对象分给多个线程各自搜索 delta,结果因线程切分而不同——我在没固定线程数时观察到 window=2 的 pack 在两次运行中分别是 83,277 和 96,588 字节);数据是固定随机种子生成的文本,每次改 3 行加 1 行;耗时每档 9 次取中位数
    • 没有验证:多线程下的 pack 大小分布(只观察到不固定线程数时结果不稳定);真实代码仓库(这里是随机单词);大于 22 KB 的文件、几百个相似但不相邻的对象下的 window 影响;pack.windowMemory、pack.deltaCacheSize 对结果的影响;depth 大于 50 的收益
    京ICP备2026067502号-1京公网安备11010502063487号