bigywater.tech
Git · 16

大文件的 blob:压缩率与每次修改的存储代价

一个 1 MB 的文件在 git 里占多少空间?每改一行会多占多少?这篇测不同类型文件的 blob 大小、每次修改的松散对象增长、gc 之后的缩小,以及压缩级别和随机二进制文件的 delta。

43%
1 MB 文本压缩后的松散对象大小占原文件的比例(随机字节是 100.0%)
430 KB
改一行再提交,松散对象每次新增的大小
2.58 MB → 391 KB
6 个版本的 objects 目录大小,gc 前后

blob 的大小取决于可压缩性

实测 · 本机执行输出
  $ git add -A
  text.txt    原始 1,000,050 字节 → 松散对象   430,279 字节  (43.0%)
  random.bin  原始 1,000,000 字节 → 松散对象 1,000,324 字节  (100.0%)
  zeros.bin   原始 1,000,000 字节 → 松散对象     4,395 字节  (0.4%)

blob 先 zlib 压缩再存。文本约压到 43%,随机字节没法压缩,反而因为头部多出几百字节,全零几乎不占空间。不要拿原文件大小估算仓库大小。

松散对象每版一份

实测 · 本机执行输出
  提交 v0 之后 objects 目录: 430,446 字节
  提交 v1 之后: 860,986 字节(这次新增 430,540)
  提交 v2 之后: 1,291,543 字节(这次新增 430,557)
  提交 v3 之后: 1,722,106 字节(这次新增 430,563)
  提交 v4 之后: 2,152,685 字节(这次新增 430,579)
  提交 v5 之后: 2,583,273 字节(这次新增 430,588)
  $ git count-objects -v | grep -E '^(count|size|in-pack|size-pack)'
  count: 18
  size: 2592
  in-pack: 0
  size-pack: 0
  size-garbage: 0

每提交一个新版本,不管只改了一行还是一万行,都新增一个完整的压缩副本。1 MB 文本改 5 次,objects 目录就是 2.58 MB。松散对象本身没有跨版本的复用。

gc 把差异存起来

实测 · 本机执行输出
  $ git gc -q; git count-objects -v | grep -E "^(count|size|in-pack|size-pack)"
  count: 0
  size: 0
  in-pack: 18
  size-pack: 380
  size-garbage: 0
  objects 目录总大小: 391,354 字节(gc 前 2,583,273)

git gc 把对象打进 packfile,版本之间用 delta 编码。同样 6 个版本,目录从 2.58 MB 缩到 391 KB。这是 git 能放得下很长历史的原因(见后面 packfile 与 delta 的文章)。

压缩级别

实测 · 本机执行输出
  level 0: 松散对象 1,000,219 字节  add 耗时 18 ms
  level 1: 松散对象   430,279 字节  add 耗时 25 ms
  level 4: 松散对象   390,613 字节  add 耗时 29 ms
  level 6: 松散对象   386,597 字节  add 耗时 32 ms
  level 9: 松散对象   386,597 字节  add 耗时 32 ms

core.compression 调的是 zlib 级别。默认松散对象用 level 1;提高到 4 省约 9%,再往上几乎无变化,耗时也只是十几毫秒的差别。真正的收益来自 delta,不是调高压缩级别。

二进制文件

实测 · 本机执行输出
  gc 前 objects: 2,001,016 字节
  $ git gc -q; git count-objects -v | grep -E "^(in-pack|size-pack)"
  in-pack: 6
  size-pack: 978
  gc 后 objects 目录: 1,003,371 字节(两个版本的原始字节共 2,000,000)

delta 按字节差异存储,与可不可压缩无关。1 MB 随机文件改 10 个字节,gc 之后两个版本只比一个版本多一点点。真正会让仓库膨胀的是整个重新生成的大文件(每次内容全变),而不是小改动。

分步演示

大文件的存储代价
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。测试数据是固定随机种子生成的 1 MB 文本(随机单词)、随机字节、全零;大小是 .git/objects 下文件的字节数,count-objects 的 size 单位是 KiB;耗时每个级别只测一次,含进程启动
    • 没有验证:真实源代码或图片等文件的压缩率(这里的文本是随机单词拼成的);--depth、--window 对 gc 结果的影响(见 delta 链一篇);core.bigFileThreshold(超过 512 MiB 的文件不做 delta,没有试);Git LFS;压缩耗时的稳定数字(只跑了一次)
    京ICP备2026067502号-1京公网安备11010502063487号