缓慢的觉醒

你已经在数据科学项目上工作了一段时间。有二十个 notebook,一些图片,还有那种三个月前看起来不错的典型文件夹结构。

你执行 git status 想看看改了什么,然后… 等待。继续等待。在等待的时候你开始怀疑是不是电脑卡死了还是在沉思什么。

剧透:它不是在沉思。它在痛苦。

问题有名有姓

Git 不慢。你的仓库慢。

当你执行 git status 时,git 需要做两件看起来简单但实际很复杂的事情:

  1. 扫描整个文件树 看看有什么变化
  2. 比较每个文件 与保存的版本

在普通仓库里这是瞬间完成的。但 Jupyter notebook 是伪装成文档的 JSON。而且不是普通的 JSON:是包含代码、输出、base64 编码图片、内核元数据,以及设计这个格式的人能想到的所有东西的 JSON。

一个带几张图表的"小" notebook 可能有几兆大小。乘以二十个 notebook,你就有了一个每次看它都在呻吟的仓库。

如果你还重命名了文件夹… git 会理解为"你删除了 50 个文件然后创建了 50 个新文件"。保证有趣。

解决方案 1:给你的仓库配个门卫

第一个解决方案优雅得让人恨不得早点知道。

叫做 FSMonitor,工作原理是:不让 git 每次都扫描整个仓库,而是操作系统告诉它哪些文件变了。

用人话说:就像在门口有个门卫告诉你"只有小王进来了",而不用每次都检查整个宾客名单。

激活方法:

1
2
git config core.fsmonitor true
git config core.untrackedcache true

搞定。就这样。

激活后第一次执行 git status 还是会花同样(或更多)时间,因为要初始化缓存。但从第二次开始… 魔法。

在我那个 400+ 文件的仓库里,git status 从 2-3 秒变成了瞬间完成。不是"快"。是瞬间。

在所有平台都能用吗?

  • macOS: 可以,使用 FSEvents
  • Linux: 可以,使用 inotify(如果内核支持的话,肯定支持)
  • Windows: 可以,使用 ReadDirectoryChangesW

总之,可以。

解决方案 2:保存食谱,不保存菜品照片

FSMonitor 加速了扫描,但还有另一个问题:notebook 仍然很大。每次你执行一个单元格并保存时,即使代码相同,文件也会变化因为输出不同。

这意味着:

  • 无法阅读的 diff(谁想看图片的 base64?)
  • 膨胀的提交
  • 地狱般的合并

解决方案叫做 nbstripout,正如其名所示:在提交前剥离 notebook 的输出。

就像保存食谱而不保存成品照片。代码在那里,需要时重新生成结果。

用 uv 安装:

1
2
uv add nbstripout
uv run nbstripout --install

或用 pip,如果你是老派的话:

1
2
pip install nbstripout
nbstripout --install

--install 自动配置 git 使用 nbstripout 作为过滤器。从那时起,当你提交 notebook 时,会保存为无输出版本。

验证是否工作:

1
git config --get filter.nbstripout.clean

如果返回类似 nbstripout 的内容,就对了。

如果我需要保存输出怎么办?

好问题。有时你想提交已执行的 notebook,比如用于文档或让别人无需执行就能查看。

nbstripout 让你标记特定 notebook 跳过过滤器:

1
git -c diff.nbstripout.textconv=cat diff

或者在 .gitattributes 中配置例外。但总的来说,我的建议是:不要保存输出。Notebook 是代码,不是最终文档。

荣誉提及:git-lfs

如果除了 notebook 你还有频繁变化的图片或视频,存在 git-lfs(Large File Storage)。

想法很简单:不在仓库里保存大型二进制文件,而是保存指针,真实文件存在单独的服务器上。

就像把行李存在机场寄存处,只带着取票。

1
2
3
git lfs install
git lfs track "*.png"
git lfs track "*.mp4"

为什么只是荣誉提及而不是主要解决方案?因为增加了复杂性。你需要 LFS 服务器(GitHub 和 GitLab 提供,但有限制),如果有人在没安装 git-lfs 的情况下克隆仓库,得到的是指针而不是文件。

对于 notebook,FSMonitor + nbstripout 通常足够了。git-lfs 是用于真正的数据集或多媒体资源的。

制胜组合

我在任何有 notebook 的仓库中的配置:

1
2
3
4
5
6
7
# 速度
git config core.fsmonitor true
git config core.untrackedcache true

# 干净的 notebook
uv add nbstripout
uv run nbstripout --install

四个命令,你的仓库从关节炎老人变成奥运选手。

验证一切正常

1
2
3
4
5
6
7
# FSMonitor 激活
git config --get core.fsmonitor
# 应该返回: true

# nbstripout 配置
git config --get filter.nbstripout.clean
# 应该返回: nbstripout

如果两个都返回预期结果,就完成了。享受你瞬间完成的 git status 和可读的 diff 吧。

总结

下次有人告诉你"git 很慢",你就知道该回答什么了:git 不慢,是你的仓库配置不当。

用 FSMonitor 让操作系统干脏活累活。用 nbstripout 让 notebook 不像背着铅块一样重。

如果做完这些还是慢… 也许该考虑是否真的需要在同一个仓库里放 200 个 notebook。但那是另一个故事了。