如何处理未跟踪的文件并提高Git状态性能

5

我目前使用的是Mac电脑。

在Git 2.35.1版本中,当我克隆我的代码库时,需要7秒钟来枚举未跟踪的文件。当我执行time git status命令时,大约需要2秒钟。 而当我切换到其他分支时,需要大约15秒钟,当我切换回我的主代码库时,git status需要15秒钟(这不应该花费这么长时间)。

在(2.35.1)中的解决方法是: 我设置了core.untrackedCache=trueGIT_FORCE_UNTRACKED_CACHE=1, 这有助于更新未跟踪的缓存以提高git status性能,时间约为4秒,这在大多数Stack Overflow答案中都有提到。 stack-overflow问题

但现在在Git 2.36.1版本中,这种解决方法似乎不起作用了。在所有分支上都需要大约20秒钟。

可能的代码更改:

在Git 2.35.1中,dir.c的代码如下:

if (dir->untracked) {
        static int force_untracked_cache = -1;

        if (force_untracked_cache < 0)
            force_untracked_cache =
                git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0);
        if (force_untracked_cache &&
            dir->untracked == istate->untracked &&
            (dir->untracked->dir_opened ||
             dir->untracked->gitignore_invalidated ||
             dir->untracked->dir_invalidated))
            istate->cache_changed |= UNTRACKED_CHANGED;
        if (dir->untracked != istate->untracked) {
            FREE_AND_NULL(dir->untracked);
        }
    }

在 Git 2.36.1 中,dir.c 中的代码相同:

if (dir->untracked) {
        static int force_untracked_cache = -1;

        if (force_untracked_cache < 0)
            force_untracked_cache =
                git_env_bool("GIT_FORCE_UNTRACKED_CACHE", -1);
        if (force_untracked_cache < 0)
            force_untracked_cache = (istate->repo->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE);
        if (force_untracked_cache &&
            dir->untracked == istate->untracked &&
            (dir->untracked->dir_opened ||
             dir->untracked->gitignore_invalidated ||
             dir->untracked->dir_invalidated))
            istate->cache_changed |= UNTRACKED_CHANGED;
        if (dir->untracked != istate->untracked) {
            FREE_AND_NULL(dir->untracked);
        }
    }

编辑 1:

GIT_TRACE_PERFORMANCE=1 git status

12:44:54.433726 read-cache.c:2437       performance: 0.092473000 s: read cache .git/index
12:44:54.915510 read-cache.c:2480       performance: 0.481510000 s: read cache .git/sharedindex.f6119c27ffbee28b22e1baa47e66f355491292e
12:45:05.369546 preload-index.c:154     performance: 10.374954000 s: preload index
Refresh index: 100% (1164397/1164397), done.
12:45:05.421952 read-cache.c:1721       performance: 10.427363000 s: refresh index
12:45:05.464869 diff-lib.c:266          performance: 0.040042000 s:  diff-files
12:45:05.478549 unpack-trees.c:1884     performance: 0.000028000 s: traverse_trees
12:45:05.493406 unpack-trees.c:424      performance: 0.000008000 s:check_updates
12:45:05.493444 unpack-trees.c:1974     performance: 0.028052000 s: unpack_trees
12:45:05.493454 diff-lib.c:629          performance: 0.028099000 s:  diff-index
On branch default

Your branch is up to date with 'origin/default'.

当我切换分支并回到默认分支时,以下是性能情况。我不确定为什么下面的read-cache.c会占用这么多时间!

GIT_TRACE_PERFORMANCE=1 git status
12:22:24.343325 read-cache.c:2437       performance: 0.112630000 s: read cache .git/index
12:22:42.618493 read-cache.c:2480       performance: 18.274836000 s:read cache .git/sharedindex.5ad8766e997830f32884b42ca5b17c2be6a19f1
12:22:53.559907 preload-index.c:154     performance: 10.840555000 s: preload index
Refresh index: 100% (1164397/1164397), done.
12:22:53.646110 read-cache.c:1721       performance: 10.926760000 s: refresh index
12:22:53.685650 diff-lib.c:266          performance: 0.038002000 s:  diff-files
12:22:53.713422 unpack-trees.c:1884     performance: 0.000042000 s: traverse_trees
12:22:53.726052 unpack-trees.c:424      performance: 0.000008000 s: check_updates
12:22:53.726085 unpack-trees.c:1974     performance: 0.028672000 s:unpack_trees
12:22:53.726094 diff-lib.c:629          performance: 0.039895000 s:  diff-index
12:23:03.568051 read-cache.c:3121       performance: 0.161937000 s: write index, changed mask = c
On branch default

Your branch is up to date with 'origin/default'.

You are in a sparse checkout with  tracked files present.
Changes not staged for commit:
 Modified:
 Modified:
….

编辑2:

我做了一些研究,发现当我设置 core.splitindex=true 时会创建 .git/sharedindex. 文件,并且 sharedindex 占用了时间。那么这与性能有关吗?

如何解决未跟踪文件缓存性能问题?有没有解决方法?


你能够可靠地重现这个问题吗?我尝试了但没有成功。最好有确切的步骤(从一个最小的~/.gitconfig开始,克隆一些公共的repo,例如Git本身,或者Linux内核如果需要一个巨大的repo)。很好地识别了“read cache .git/sharedindex”,也许一个Git专家可以从那里解决它。(注意:GIT_FORCE_UNTRACKEDCACHE应该是GIT_FORCE_UNTRACKED_CACHE,在你的问题中我已经修复了它。) - tom
3个回答

2
那个变化来自于我提出的提交26b8946,我在“如何摆脱警告"此系统上未跟踪缓存已禁用"的问题”中介绍了它。
它修复了设置core.untrackedCache的问题,当设置为true时,无法将未跟踪的缓存扩展添加到索引中。

在你的情况下,也许是自动将未跟踪的缓存扩展添加到索引中造成了问题。

查看提交26b8946(2022年2月17日)由Derrick Stolee (derrickstolee)完成。
(合并于提交80f7f61,2022年2月25日,由Junio C Hamano -- gitster --进行)

dir:使用core.untrackedCache强制未跟踪缓存

签名作者:Derrick Stolee

GIT_FORCE_UNTRACKED_CACHE 环境变量比 core.untrackedCache 配置变量更频繁地写入未跟踪缓存。
这是由于 read_directory() 处理未跟踪缓存创建的方式所致。

在此更改之前,Git 不会为没有未跟踪缓存扩展的索引创建未跟踪缓存。
用户需要运行诸如 'git update-index --untracked-cache'(man) 这样的命令,以便索引实际包含未跟踪缓存。

特别是,即使设置了 core.untrackedCache=true,用户也注意到未跟踪缓存不会出现。
一些用户报告在其工程系统环境中设置 GIT_FORCE_UNTRACKED_CACHE=1 以确保未跟踪缓存将被创建。

不写入未跟踪缓存的决定可以追溯到 fc9ecbe(“dir.c} }: don't flag the index as dirty for changes to the untracked cache”,2018-02-05,Git v2.17.0-rc0 - {{link5:merge listed in batch #8)。
这个更改的动机是写入索引很昂贵,如果只需要写入未跟踪缓存,那么它比缓存的好处更昂贵。
然而,这也意味着未跟踪缓存永远不会被填充,因此启用该配置的用户实际上直到手动运行 'git update-index --untracked-cache' 或使用环境变量才能获取扩展。

我们在 microsoft/git 分支中已经有了这个更改的版本,已经发布了几个重要版本。
它一直有效地使用户处于良好状态。
是的,第一次索引写入很慢,但剩余的索引写入比没有这个更改要快得多。

所以,不要将GIT_FORCE_UNTRACKED_CACHE设置为1(保持core.untrackedCachetrue),而是取消设置,并在git statusgit switch之前手动运行git update-index --untracked-cache。然后测试性能是否可接受(这只是一个测试,而不是最终的解决方法)。

1
我尝试了这个方法,但并没有帮助。回归结果相同。刚开始克隆存储库的时候,枚举未跟踪文件需要7秒钟,Git状态需要2秒钟。但是当我在分支之间来回切换时,枚举未跟踪文件要花费10秒钟,Git状态要花费20秒钟。 - checked
请注意:两个分支的文件存在很大差异,并且文件名称是区分大小写的。 - checked
1
@checked 你可以尝试在Git版本中设置TRACE2环境变量,例如 export GIT_TRACE2_PERFORMANCE=1:这将有助于确定哪些操作需要时间。 - VonC
笔误:应该是 GIT_TRACE2_PERF - tom

2

(这不是解决方案,只是一些调试建议。)

  • 您可以同时使用GIT_TRACE2_PERF=1GIT_TRACE_PERFORMANCE=1来获取更多信息。

  • 在Linux上,strace -c <command>输出系统调用统计信息,包括系统调用的总数,这是一个有用的度量标准(墙钟时间可能会受到磁盘缓存等因素的影响)。而strace <command>会逐个显示每个系统调用,这使您可以比较运行之间的执行跟踪(我喜欢使用sed 's/0x[0-9a-fA-F]*/0x?/g'过滤跟踪中的内存地址,因为每次运行的地址都不同,会产生很多噪音)。在macOS上,dtruss提供了类似的界面。

  • Git的分割索引可能会导致行为不可预测,因为Git根据分割索引中条目的数量有条件地将更改推送到共享索引中。您可以通过复制整个存储库(使用cp -rp以保留时间戳)并在每个副本中运行相同的命令序列(一个副本中使用Git 2.35.1,另一个副本中使用Git 2.36.1)来控制此问题。

  • “竞争时间戳”可能会导致诸如git status之类的命令表现出不同的行为,具体取决于更改文件后您运行它们的速度。您可能需要等待一两秒钟才能稳定地运行git status。(还要注意,git status在某些情况下会修改索引,因此如果再次运行它,则可能会表现出不同的行为。)

  • 如果您能够从源代码构建Git(并且可以可靠地重现问题),则可以使用git bisect查找错误提交(只需要大约9个构建和测试步骤即可将v2.35.1二分到v2.36.1)。


1
我注意到一件事情,将core.splitindex设置为默认值(即False)可以使回归恢复正常,大约需要3/4秒。core.splitindex和untracked_cache之间是否有关联?当我们有两个分割索引文件(index和sharedindex.<sha>)时,untracked_cache是如何更新的? - checked

0

目前在Git 2.36.1中,我似乎找不到有效管理未跟踪缓存的答案。

因此,为了解决当前的回归问题,我尝试在两个独立的克隆中克隆2个分支(默认分支和补丁分支)。这样可以提高性能,现在我不需要切换分支。

git clone --branch <branchname> <remote-repo-url>

我希望通过这个解决方案获得更好的git状态性能,但我仍然希望在单个克隆中拥有两个分支,并且具有更好的性能。如果我找到任何解决方案,我将在此发布。

谢谢。


2
如果需要在仅克隆一次的情况下切换分支,请不要忘记 git worktree:两个不同的文件夹,一个用于每个分支,但来自相同的克隆仓库。 - VonC
哦,我不知道这个。非常感谢,我会去了解一下的。 - checked

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接