大仓库里 git status 为什么慢,untracked cache 和 fsmonitor 又各自省了什么?这篇在 1 万和 10 万个文件的仓库上分别测:先看 lstat 次数,再看耗时,最后看守护进程的代价。结论有点出人意料:1 万个文件时它们几乎没用。
$ git status --short; git ls-files | wc -l; find build -type f | wc -l
?? build/
10000
3000
git status 耗时(5 次中位数): 32.0 ms;lstat 次数(preload 与 refresh 之和): 10000git status 要对每个已跟踪文件 lstat 一次(用 preload-index 并行),还要扫描目录找出未跟踪的文件。1 万个文件时整个 status 只要约 30 ms。
$ git update-index --test-untracked-cache 2>&1 | tail -1; git config core.untrackedCache true; git status --short Testing mtime in '/work/l33a' ...... OK ?? build/ index 里的扩展块: ['TREE', 'UNTR'] + untracked cache: 28.3 ms;lstat 次数 10000 $ git config core.fsmonitor true; git status --short; sleep 1; git fsmonitor--daemon status 2>&1 | sed -E "s#'.*'#'<repo>'#" ?? build/ fsmonitor-daemon is watching '<repo>' index 里的扩展块: ['TREE', 'UNTR', 'FSMN'] + fsmonitor: 30.1 ms;lstat 次数 0 (1 万个文件时 lstat 本来就很快:git 用 preload-index 多线程并行 lstat,1 万次只要约 9 毫秒)
core.untrackedCache 把每个目录上次的扫描结果(及目录 mtime)存进 index 的 UNTR 扩展,目录没变就不再扫描。core.fsmonitor 启动守护进程监听文件系统事件,index 的 FSMN 扩展记着上次查询的标记,status 只需问“之后哪些路径变了”,不再 lstat 全部文件。
$ git ls-files | wc -l; find build -type f | wc -l
100000
20000
基线 git status: 184.4 ms;lstat 次数 100000
+ untracked cache: 102.8 ms
+ fsmonitor(两个都开): 38.3 ms;lstat 次数 0
只开 fsmonitor(关掉 untracked cache): 88.3 ms1 万个文件时 lstat 从 10000 次降到 0,耗时却没变(并行 lstat 本来就快)。10 万个文件时才显出差别:184 ms 降到 untracked cache 的 103 ms,两者叠加的 38 ms。仓库不大时不值得打开。
$ git status --short M src/d123/f061700.txt ?? build/ git status: 89.9 ms(fsmonitor 开启)
守护进程持续接收文件系统事件。改一个文件、新建一个文件之后 status 立刻能看到,仍不需要全量扫描。
$ git fsmonitor--daemon status 2>&1 | sed -E "s#'.*'#'<repo>'#"; ls .git | grep fsmonitor | tr " " " "; echo fsmonitor-daemon is watching '<repo>' fsmonitor--daemon fsmonitor--daemon.ipc $ git config --unset core.fsmonitor; git fsmonitor--daemon status 2>&1 | sed -E "s#'.*'#'<repo>'#" fsmonitor-daemon is watching '<repo>' $ git fsmonitor--daemon stop; git fsmonitor--daemon status 2>&1 | sed -E "s#'.*'#'<repo>'#"; ls .git | grep fsmonitor | tr " " " "; echo fsmonitor-daemon is not watching '<repo>' fsmonitor--daemon 全部关闭之后: 154.8 ms
每个仓库一个常驻进程,有 ipc socket。把 core.fsmonitor 配置关掉并不会停掉它,需要 git fsmonitor--daemon stop。