Git的索引和提交非常缓慢。

10
我有一个本地的Git仓库,并使用git add 文件1 文件2 文件3...将我的修改添加到Git索引中。之后我使用纯粹的git commit命令。每个命令需要大约3到6秒钟时间。 我的仓库已有大约150000个提交。
我已经执行了git gc,因为我认为它会执行一些垃圾回收。SSD相当快。 我想知道我可以在Git中调整哪些参数来加速这两个命令的执行。 有任何建议吗?
git version 2.21.0 (Apple Git-122.2)

我的系统是一台Mac Pro,运行的是macOS 10.14.6操作系统。我使用的是带有APFS文件系统的SSD硬盘。我没有安装任何防病毒软件(或任何其他干扰扫描软件)。


提交的次数不应该有影响,但是被修改的文件数量可能会有所不同。你添加了多少个文件?git addgit commit 都需要 3 到 6 秒钟吗,还是只有 git commit?其他命令如 git loggit diff 是否也很慢? - Schwern
2
你可以运行 GIT_TRACE_PERFORMANCE=1 git <cmd> 来查看 Git 花费时间的位置。 - Henri Menke
还要更新 Git,因为每个版本都引入了性能改进,特别是对于大型代码库来说。 - Philippe
1
您添加了什么类型的文件?每当您提交更改时,Git 都会使用 zlib 压缩对象,如果您的文件是二进制对象,则该压缩将消耗一些 CPU 周期。因此,Git 真正应该只用于文本文件。如果您要添加像图像、媒体、应用程序二进制/存档等二进制对象,则应使用 git-lfs 以便 Git 可以将这些对象未压缩地存储在单独的位置上。 - daveruinseverything
1个回答

11
首先,更新到最新的Git 2.25版本:每个新版本都会解决性能问题。
要调查性能问题,请将环境变量GIT_TRACE2_PERF设置为1并运行git命令。详情请参见此SO答案,了解有关trace2功能以及如何解释输出表的详细信息。
(在Bash中,您可以在同一行中设置变量并运行命令
GIT_TRACE2_PERF=1 git commit -m "test"

在Windows命令提示符中,您需要使用SET命令:
SET GIT_TRACE2_PERF=1
git commit -m "test"

或者,在一行CMD命令中:(参考链接)

cmd /V /C "SET "GIT_TRACE2_PERF=1" && git commit -m "test""

例如,在Windows上,您将看到类似以下输出的信息:
C:\git\me\foobar>SET GIT_TRACE2_PERF=1

C:\git\me\foobar>git status
17:23:13.056175 common-main.c:48             | d0 | main                     | version      |     |           |           |              | 2.31.1.windows.1
17:23:13.056175 common-main.c:49             | d0 | main                     | start        |     |  0.003356 |           |              | git.exe status
17:23:13.065174 ..._win32_process_info.c:118 | d0 | main                     | data_json    | r0  |  0.012053 |  0.012053 | process      | windows/ancestry:["git.exe","cmd.exe","explorer.exe"]
17:23:13.066174 repository.c:130             | d0 | main                     | def_repo     | r1  |           |           |              | worktree:C:/git/me/foobar
17:23:13.067174 git.c:448                    | d0 | main                     | cmd_name     |     |           |           |              | status (status)
17:23:13.068174 read-cache.c:2324            | d0 | main                     | region_enter | r1  |  0.015462 |           | index        | label:do_read_index .git/index
17:23:13.069175 cache-tree.c:598             | d0 | main                     | region_enter | r1  |  0.015809 |           | cache_tree   | ..label:read
17:23:13.069175 cache-tree.c:600             | d0 | main                     | region_leave | r1  |  0.016021 |  0.000212 | cache_tree   | ..label:read
17:23:13.069175 read-cache.c:2284            | d0 | main                     | data         | r1  |  0.016056 |  0.000594 | index        | ..read/version:2
17:23:13.069175 read-cache.c:2286            | d0 | main                     | data         | r1  |  0.016065 |  0.000603 | index        | ..read/cache_nr:3808
17:23:13.069175 read-cache.c:2329            | d0 | main                     | region_leave | r1  |  0.016072 |  0.000610 | index        | label:do_read_index .git/index

请注意左侧列中的墙钟时间,第7列中自启动以来的总时间,以及每个子操作的总时间在第8列中。
请注意:从 Git 2.40(2023年Q1)开始,您可以加速写入索引!
Git 2.40引入了一个可选配置,允许“跳过”保护索引文件免受位翻转的尾部哈希值。
请参见提交17194b1、da9acde、ee1f0c2和1687150(2023年1月6日),作者是Derrick Stolee(derrickstolee)。
(由Junio C Hamano(gitster)于2023年1月16日在提交ffd9238中合并)

hashfile: 允许跳过哈希函数

共同作者:Kevin Willford
签署者:Kevin Willford
签署者:Derrick Stolee

hashfile API用于生成包含文件内容的尾部哈希的文件。使用此哈希可帮助验证文件是否被破坏,例如由于故障的驱动器导致了位翻转。Git的索引文件包括此尾随哈希,因此它使用“struct hashfile”来处理文件的输入/输出操作。这对于在这些操作期间使用hashfile方法非常方便。但是,在写入过程中对文件内容进行哈希计算会导致性能下降。与不经过此步骤的字节相比,将字节哈希到磁盘上会更慢。硬件加速的SHA1计算被软件基础的sha1dc计算所取代,这使得这个问题变得更加严重。这种写入成本很高,而校验和功能可能不值得为如此短暂的文件付出这种代价。索引经常被重新编写,检查校验和的唯一时间是在'git fsck'期间。因此,允许用户选择退出哈希计算将是有帮助的。首先,我们需要允许Git在hashfile API中选择退出哈希计算。API的缓冲写入仍然是有用的,因此在这里进行更改是合理的。引入一个新的“skip_hash”选项到"struct hashfile"中。当设置后,"the_hash_algo"的“update_fn”和“final_fn”成员将被跳过。在完成哈希文件时,尾随哈希将被替换为null hash。在任何情况下都希望使用这种尾随null哈希,因为我们不想特别处理具有不同长度的文件格式,具体取决于它是否被哈希。当文件的最终字节全部为零时,我们可以推断出它是未经哈希写入的,因此验证不可用作文件一致性检查。这也意味着我们可以轻松地为任何所需的文件格式切换哈希计算。此补丁的一个版本自2017年以来一直存在于microsoft/git分支中(链接的提交已在2018年重组,但原始提交可以追溯到2017年1月)。在这里,使索引使用这个快速路径的更改被推迟到后面的更改。

read-cache: 添加了 index.skipHash 配置选项

Signed-off-by: Derrick Stolee

The previous change allowed skipping the hashing portion of the hashwrite API, using it instead as a buffered write API.
Disabling the hashwrite can be particularly helpful when the write operation is in a critical path.

One such critical path is the writing of the index.
This operation is so critical that the sparse index was created specifically to reduce the size of the index to make these writes (and reads) faster.

This trade-off between file stability at rest and write-time performance is not easy to balance.
The index is an interesting case for a couple reasons:

  1. Writes block users.
    Writing the index takes place in many user- blocking foreground operations. The speed improvement directly impacts their use. Other file formats are typically written in the background (commit-graph, multi-pack-index) or are super-critical to correctness (pack-files).

  2. Index files are short lived.
    It is rare that a user leaves an index for a long time with many staged changes. Outside of staged changes, the index can be completely destroyed and rewritten with minimal impact to the user.

Following a similar approach to one used in the microsoft/git fork, add a new config option (index.skipHash) that allows disabling this hashing during the index write.
The cost is that we can no longer validate the contents for corruption-at-rest using the trailing hash.

We load this config from the repository config given by istate->repo, with a fallback to the_repository if it is not set.

While older Git versions will not recognize the null hash as a special case, the file format itself is still being met in terms of its structure.
Using this null hash will still allow Git operations to function across older versions.

The one exception is 'git fsck'(man) which checks the hash of the index file.
This used to be a check on every index read, but was split out to just the index in a33fc72 (read-cache: force_verify_index_checksum, 2017-04-14, Git v2.13.0-rc1 -- merge) (read-cache: force_verify_index_checksum, 2017-04-14) and released first in Git 2.13.0.
Document the versions that relaxed these restrictions, with the optimistic expectation that this change will be included in Git 2.40.0.

Here, we disable this check if the trailing hash is all zeroes.
We add a warning to the config option that this may cause undesirable behavior with older Git versions.

As a quick comparison, I tested 'git update-index'(man) --force-write with and without index.skipHash=true on a copy of the Linux kernel repository.

Benchmark 1: with hash
    Time (mean ± σ):      46.3 ms ±  13.8 ms    [User: 34.3 ms, System: 11.9 ms]
    Range (min  max):    34.3 ms   79.1 ms    82 runs

Benchmark 2: without hash
    Time (mean ± σ):      26.0 ms ±   7.9 ms    [User: 11.8 ms, System: 14.2 ms]
    Range (min  max):    16.3 ms   42.0 ms    69 runs

Summary
    'without hash' ran
      1.78 ± 0.76 times faster than 'with hash'

These performance benefits are substantial enough to allow users the ability to opt-in to this feature, even with the potential confusion with older 'git fsck' versions.

git config现在在其手册页中包括:

index.skipHash

启用此功能后,将不会计算索引文件的尾部哈希值。这将加速操作索引的Git命令,例如git addgit commitgit status

相反地,存储一个所有字节均为零的尾部序列,表示跳过了哈希计算。

如果启用了index.skipHash,则早于2.13.0版本的Git客户端将拒绝解析索引,并且早于2.40.0版本的Git客户端将在git fsck期间报告错误。


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