bigywater.tech
Git · 20

multi-pack-index

multi-pack-index 是什么,为什么 pack 一多 git 就需要它?这篇造 20 个小 pack,解析 midx 文件的头部和四个块,验证偏移,测有无 midx 的查找耗时,再用 multi-pack-index repack 与 expire 把小 pack 合并。

20 → 11
多次 multi-pack-index repack 与 expire 之后 pack 的数量,对象数始终 3060
4 个块
midx 里的 PNAM、OIDF、OIDL、OOFF,合起来为 20 个 pack 提供一张统一的索引
14.3 vs 14.6 ms
20 个 pack 下有无 midx 查全部 3060 个对象的耗时,几乎无差别

为什么需要

实测 · 本机执行输出
  $ ls .git/objects/pack/*.pack | wc -l; git count-objects -v | grep -E "^(count|in-pack|packs)"
        20
  count: 0
  in-pack: 3060
  packs: 20

每个 pack 有自己的 .idx,查一个对象要依次在多个 idx 里找。multi-pack-index 把所有 pack 的对象放进一张全局索引:去重的 hash 表、全局 fanout、每个对象所在的 pack 和偏移,查一次就知道在哪。

文件结构

实测 · 本机执行输出
  $ git multi-pack-index write; ls -l .git/objects/pack/multi-pack-index | awk '{print $5, $9}'
  87796 .git/objects/pack/multi-pack-index
  签名 b'MIDX'  版本 1  哈希版本 1(1 = SHA-1)  块数 4  基础 midx 数 0  pack 数 20
  0000  4d 49 44 58 01 01 04 00 00 00 00 14 50 4e 41 4d  MIDX........PNAM
  0010  00 00 00 00 00 00 00 48 4f 49 44 46 00 00 00 00  .......HOIDF....
  …(共 87796 字节)
  块 PNAM  偏移     72  长度 1000
  块 OIDF  偏移   1072  长度 1024
  块 OIDL  偏移   2096  长度 61200
  块 OOFF  偏移  63296  长度 24480

头部 12 字节:签名 MIDX、版本、哈希版本、块数、基础 midx 数、pack 数;然后是块表(每个块的 4 字节 id 加 8 字节偏移),再是 PNAM(pack 名字)、OIDF(fanout)、OIDL(hash 表)、OOFF(pack 编号加偏移)。

对账

实测 · 本机执行输出
  PNAM:20 个 pack 名字(按字典序),前 3 个:
    pack-05d19cb0c0f12ec488785d2814ac095f8b949eb6.idx
    pack-0b99932a485532f20674488e7c3b2005e1dd487a.idx
    pack-0c044d7a4fe72a4543eb851cb28c8ab13f17f581.idx
  OIDF 末项 = 3060 个对象(去重后);各 pack 的 in-pack 之和: 3060
  OIDL 升序排列: True
  目标 ee29687a3197…:OOFF 里 pack 编号 19 → pack-fa345978418344774a83c897ef5c20e1f6fe9279.idx,偏移 3838
  该 pack 里 git verify-pack 给出的偏移 3838  一致: True
  $ git multi-pack-index verify 2>&1; echo "verify 退出码 $?"
  verify 退出码 0

midx 里记录的 pack 编号与偏移,和 git verify-pack 给出的偏移完全一致;git multi-pack-index verify 通过。

速度

实测 · 本机执行输出
  20 个 pack,使用 midx : 14.3 ms
  20 个 pack,不使用 midx(core.multiPackIndex=false): 14.6 ms

本例 20 个 pack、3060 个对象,有没有 midx 查找耗时没有差别。midx 的价值在于 pack 很多的大仓库,以及位图等需要统一对象顺序的功能(见后面 reachability bitmap 一篇)。

合并小 pack

实测 · 本机执行输出
  现有 20 个 pack,大小 9,250 ~ 9,858 字节,合计 191,084
  $ git multi-pack-index repack --batch-size=1; ls .git/objects/pack/*.pack | wc -l
        20
  --batch-size=1:没有产生新 pack,pack 数仍是 20
  $ git multi-pack-index repack --batch-size=30000; ls .git/objects/pack/*.pack | wc -l
        21
  $ git multi-pack-index expire; ls .git/objects/pack/*.pack | wc -l; git multi-pack-index verify && echo verify 通过; git count-objects -v | grep -E "^(in-pack|packs)"
        17
  verify 通过
  in-pack: 3060
  packs: 17
  $ git multi-pack-index repack --batch-size=60000; ls .git/objects/pack/*.pack | wc -l
        18
  $ git multi-pack-index expire; ls .git/objects/pack/*.pack | wc -l; git multi-pack-index verify && echo verify 通过; git count-objects -v | grep -E "^(in-pack|packs)"
        11
  verify 通过
  in-pack: 3060
  packs: 11

multi-pack-index repack --batch-size=N 选出一批小 pack 合成一个新 pack,expire 再删除对象已被完全覆盖的旧 pack。不需要像 git gc 那样重写全部对象,适合大仓库的增量维护。

分步演示

multi-pack-index
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。HOME 里的全局配置关闭了 maintenance.auto 和 gc.auto,否则 git commit 后会自动触发后台维护;pack 由 git repack -d 逐个生成;midx 由我用 Python 按字节解析,对账对象是 git verify-pack 与 git multi-pack-index verify;速度是 9 次中位数
    • 没有验证:几百个 pack 以上时 midx 的查找优势;incremental midx(基础 midx 链,本例基础数为 0);--preferred-pack 与 bitmap;LOFF 块(超过 2 GB 的偏移);batch-size 如何挑选 pack 的具体规则(只记录了本例的结果);默认配置下自动维护合并 pack 的触发条件(只观察到现象)
    京ICP备2026067502号-1京公网安备11010502063487号