【问题标题】:How to improve git log performance?如何提高 git 日志性能?
【发布时间】:2016-05-13 05:27:16
【问题描述】:

我正在尝试从以下几个存储库中提取 git 日志:

git log --pretty=format:%H\t%ae\t%an\t%at\t%s --numstat

对于较大的存储库(如 rails/rails),生成日志需要 35 多秒的时间。

有没有办法提高这种性能?

【问题讨论】:

  • 尝试将--max-count=30 用作described in the git-log documentation。你真的需要查看 Rails 项目的所有 56,000 次提交吗?
  • @msw 这个项目,不幸的是,是的。
  • Git 2.18(2018 年第二季度)应该将git log 的性能提高很多。见my answer below。

标签: git git-log


【解决方案1】:

TLDR;如mentioned in GitMerge 2019:

git config --global core.commitGraph true
git config --global gc.writeCommitGraph true
cd /path/to/repo
git commit-graph write

实际上(见最后),Git 2.24+(2019 年第三季度)不需要前两个配置:默认情况下它们是 true。

正如T4cC0re 在the comments 中提到的那样:

如果你使用的是 git 2.29 或更高版本,你应该运行:

git commit-graph write --reachable --changed-paths

这将预先计算文件路径,因此作用于文件的git log 命令也可以从这个缓存中受益。


Git 2.18(2018 年第二季度)将提高 git log 的性能:

见commit 902f5a2(2018 年 3 月 24 日)René Scharfe (rscharfe)。
请参阅commit 0aaf05b、commit 3d475f4(2018 年 3 月 22 日)Derrick Stolee (derrickstolee)。
请参阅 brian m. carlson (bk2204) 的 commit 626fd98(2018 年 3 月 22 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 51f813c,2018 年 4 月 10 日)

sha1_name:使用bsearch_pack() 表示缩写

在针对单个对象 ID 计算缩写长度时 packfile,方法find_abbrev_len_for_pack()目前实现 二进制搜索。
这是几个实现之一。
此实现的一个问题是它忽略了pack-index 中的扇出表。

翻译此二分搜索以使用现有的bsearch_pack() 方法 正确使用扇出表。

由于使用了扇出表,缩写计算为 比以前快了一点。

对于完全重新打包的 Linux 存储库副本,改进了以下“git log”命令:

* git log --oneline --parents --raw
  Before: 59.2s
  After:  56.9s
  Rel %:  -3.8%

* git log --oneline --parents
  Before: 6.48s
  After:  5.91s
  Rel %: -8.9%

相同的 Git 2.18 增加了一个提交图:预先计算和存储祖先遍历所需的信息,以优化图遍历。

参见commit 7547b95、commit 3d5df01、commit 049d51a、commit 177722b、commit 4f2542b、commit 1b70dfd、commit 2a2e32b(2018 年 4 月 10 日)和commit f237c8b、@987654341、@、@987 commit ae30d7b、commit b84f767、commit cfe8321、commit f2af9f5(2018 年 4 月 2 日)Derrick Stolee (derrickstolee)。
(由 Junio C Hamano -- gitster -- 合并到 commit b10edb2,2018 年 5 月 8 日)

commit:将提交图与提交解析集成

教 Git 检查提交图文件以提供 调用parse_commit_gently()时的结构提交。
此实现满足结构提交的所有后置条件,包括加载父级、根树和提交日期。

如果core.commitGraph 是false,则不要检查图形文件。

在测试脚本t5318-commit-graph.sh,添加output-matching条件 只读图操作。

通过从图中加载提交而不是解析提交缓冲区,我们 节省大量时间进行长时间的提交遍历。

以下是 Linux 存储库副本的一些性能结果,其中“master”有 678,653 个可访问的提交,并且比“origin/master”落后 59,929 个提交。

| Command                          | Before | After  | Rel % |
|----------------------------------|--------|--------|-------|
| log --oneline --topo-order -1000 |  8.31s |  0.94s | -88%  |
| branch -vv                       |  1.02s |  0.14s | -86%  |
| rev-list --all                   |  5.89s |  1.07s | -81%  |
| rev-list --all --objects         | 66.15s | 58.45s | -11%  |

要了解有关提交图的更多信息,请参阅“How does 'git log --graph' work?”。


相同的 Git 2.18(2018 年第二季度)添加了延迟加载树。

代码已被教导使用存储的重复信息 在提交图文件中了解提交的树对象名称 避免在有意义时打开和解析提交对象 这样做。

见commit 279ffad(2018 年 4 月 30 日)SZEDER Gábor (szeder)。
请参阅Derrick Stolee (derrickstolee) 的commit 7b8a21d、commit 2e27bd7、commit 5bb03de、commit 891435d(2018 年 4 月 6 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit c89b6e1,2018 年 5 月 23 日)

commit-graph: 延迟加载树提交

提交图文件提供对提交数据的快速访问,包括 图中每个提交的根树的 OID。表演时 深度提交图遍历,我们可能不需要加载大部分树 对于这些提交。

延迟加载从图表加载的提交的树对象 直到通过get_commit_tree()请求。
不要为不在图中的提交延迟加载树,因为这需要重复解析,并且在不需要树时相对性能改进很小。

在 Linux 存储库上,对以下项目进行了性能测试 命令:

git log --graph --oneline -1000

Before: 0.92s
After:  0.66s
Rel %: -28.3%

Git 2.21(2019 年第一季度)添加了松散缓存。

参见commit 8be88db(2019 年 1 月 7 日)和 commit 4cea1ce、commit d4e19e5、commit 0000d65(2019 年 1 月 6 日)René Scharfe (rscharfe)。
(由@987654368 中的Junio C Hamano -- gitster -- 合并@,2019 年 1 月 18 日)

object-store:每个子目录使用一个oid_array 用于松散缓存

松散对象缓存根据需要一次填充一个子目录。
它存储在oid_array 中,每次添加操作后都必须使用它。
所以在查询大范围的对象时,部分填充的数组需要最多255次,比一次排序要长100倍以上。

为每个子目录使用一个oid_array。
这确保条目只需排序一次。它还避免了每次缓存查找的八个二分搜索步骤。

缓存用于日志占位符%h、%t 和%p 的冲突检查,我们可以在存储库中看到更改速度加快了它们的速度。每个子目录 100 个对象:

$ git count-objects
  26733 objects, 68808 kilobytes

Test                        HEAD^             HEAD
--------------------------------------------------------------------
4205.1: log with %H         0.51(0.47+0.04)   0.51(0.49+0.02) +0.0%
4205.2: log with %h         0.84(0.82+0.02)   0.60(0.57+0.03) -28.6%
4205.3: log with %T         0.53(0.49+0.04)   0.52(0.48+0.03) -1.9%
4205.4: log with %t         0.84(0.80+0.04)   0.60(0.59+0.01) -28.6%
4205.5: log with %P         0.52(0.48+0.03)   0.51(0.50+0.01) -1.9%
4205.6: log with %p         0.85(0.78+0.06)   0.61(0.56+0.05) -28.2%
4205.7: log with %h-%h-%h   0.96(0.92+0.03)   0.69(0.64+0.04) -28.1%

Git 2.22(2019 年 4 月)在使用从提交图文件读取的数据之前检查错误。

参见commit 93b4405、commit 43d3561、commit 7b8ce9c、commit 67a530f、commit 61df89c、commit 2ac138d(2019 年 3 月 25 日)和commit 945944c、commit f6761fa(2019 年 2 月 21 日)@9875 .
(由 Junio C Hamano -- gitster -- 合并于 commit a5e4be2,2019 年 4 月 25 日)

commit-graph 写:如果现有图已损坏,请不要死

当commit-graph 被写入时,我们最终会调用parse_commit()。这将反过来调用代码,该代码将咨询现有的commit-graph 关于提交,如果图表损坏,我们就会死。

因此,如果设置了core.commitGraph=true,我们会进入一个失败的“commit-graph verify”不能用“commit-graph write”跟进的状态,要么需要手动删除图表才能继续,要么core.commitGraph 需要设置为“false”。

更改“commit-graph write”代码路径以使用新的 parse_commit_no_graph() 帮助程序而不是 parse_commit() 来避免这种情况。
后者将使用use_commit_graph=1 调用repo_parse_commit_internal(),如177722b 所示(“commit:集成提交图与提交解析”,2018-04-10,Git v2.18.0-rc0)。

完全不使用旧图会稍微减慢新图的写入速度,但这是防止现有提交图中的错误传播的明智方法。


使用 Git 2.24+(2019 年第三季度),提交图默认处于活动状态:

参见commit aaf633c、commit c6cc4c5、commit ad0fb65、commit 31b1de6、commit b068d9a、commit 7211b9e(2019 年 8 月 13 日)Derrick Stolee (derrickstolee)。
(由 Junio C Hamano -- gitster -- 合并commit f4f8dfe,2019 年 9 月 9 日)

commit-graph:默认开启commit-graph

commit-graph 功能在过去有很多活动 自推出以来大约一年。
该功能是中型到大型存储库的关键性能增强,并且不会显着损害小型存储库。

更改core.commitGraph 和gc.writeCommitGraph 的默认值 为 true 以便用户默认受益于此功能。


仍然使用 Git 2.24(2019 年第四季度),配置变量告诉“git fetch”在完成后写入提交图。

参见Derrick Stolee (derrickstolee) 的commit 50f26bd(2019 年 9 月 3 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 5a53509,2019 年 9 月 30 日)

fetch:添加 fetch.writeCommitGraph 配置设置

commit-graph 功能现在默认开启,默认在“git gc”期间写入。
通常,Git 仅在“git gc --auto”命令将gc.auto 设置传递给实际工作时才编写提交图。这意味着提交图将 通常落后于每天使用的提交。

要及时了解最新提交,请在“git fetch”中添加一个步骤,以便在获取新对象后编写提交图。
fetch.writeCommitGraph 配置设置 允许编写拆分提交图,因此平均而言,编写此文件的成本非常小。有时,提交图链会崩溃到一个级别,这对于非常大的存储库来说可能会很慢。

如需额外使用,请在启用feature.experimental 时将默认值调整为true。


仍然使用 Git 2.24(2019 年第四季度),commit-graph 更加强大。

参见Jeff King (peff) 的commit 6abada1、commit fbab552(2019 年 9 月 12 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit 098e8c6,2019 年 10 月 7 日)

commit-graph: 碰撞 DIE_ON_LOAD 检查实际加载时间

Commit 43d3561 (commit-graph write: 不要死如果现有的图 已损坏,2019-03-25,Git v2.22.0-rc0) 添加了我们仅在测试套件中使用的环境变量,$GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD。
但是它将对该变量的检查放在prepare_commit_graph() 的最顶部,每次我们想要使用提交图时都会调用它。
最重要的是,它在我们检查快速路径“我们是否已经尝试加载?”之前出现,这意味着我们最终会在每次使用提交图时调用getenv(),而不是仅仅在何时使用我们加载。

getenv() 被允许有意想不到的副作用,但这不应该 这里有问题;我们正在延迟加载图表,所以很明显 至少 一个 调用此函数将调用它。

但是效率低下。 getenv() 通常必须进行线性搜索 通过环境空间。

我们可以记住调用,但将检查降低到实际加载步骤仍然更简单。这对我们在 t5318 中的唯一用户来说很好,并产生了这个微小的现实世界加速:

[before]
Benchmark #1: git -C linux rev-list HEAD >/dev/null
Time (mean ± σ):      1.460 s ±  0.017 s    [User: 1.174 s, System: 0.285 s]
Range (min … max):    1.440 s …  1.491 s    10 runs

[after]
Benchmark #1: git -C linux rev-list HEAD >/dev/null
Time (mean ± σ):      1.391 s ±  0.005 s    [User: 1.118 s, System: 0.273 s]
Range (min … max):    1.385 s …  1.399 s    10 runs

Git 2.24(2019 年第四季度)还包括回归修复。

参见Derrick Stolee (derrickstolee) 的commit cb99a34、commit e88aab9(2019 年 10 月 24 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit dac1d83,2019 年 11 月 4 日)

commit-graph:修复在获取期间写入第一个提交图

报告者:Johannes Schindelin
帮助者:Jeff King
帮助者:Szeder Gábor
签字人:Derrick Stolee

之前的提交包括对 fetch.writeCommitGraph 问题的失败测试以及在带有子模块的 repo 中获取。在这里,我们修复了该错误并将测试设置为"test_expect_success"。

当<url> 的远程仓库有一个子模块时,这组命令就会出现问题。
注意 --recurse-submodules 不需要演示这个错误。

$ git clone <url> test
$ cd test
$ git -c fetch.writeCommitGraph=true fetch origin
  Computing commit graph generation numbers: 100% (12/12), done.
  BUG: commit-graph.c:886: missing parent <hash1> for commit <hash2>
  Aborted (core dumped)

作为初始修复,我将builtin/fetch.c 中调用write_commit_graph_reachable() 的代码转换为启动“git commit-graph 写入--reachable --split”进程。该代码有效,但不是我们希望该功能长期有效的方式。

该测试确实表明问题一定与“git fetch”进程的内部状态有关。

commit-graph.c 中的write_commit_graph() 方法确保我们计划编写的提交使用close_reachable()“在可达性下关闭”。
此方法从输入提交开始,并使用UNINTERESTING 标志来标记已访问过哪些提交。这允许步行花费O(N) 时间,其中N 是提交数,而不是O(P) 时间,其中P 是路径数。 (路径的数量可以是提交次数中的指数。)

但是,UNINTERESTING 标志在代码库中的很多地方都使用过。此标志通常意味着阻止提交遍历的一些障碍,例如在修订遍历中比较历史记录。
步行完成后通常不会清除它,因为这些步行的起点没有UNINTERESTING 标志,clear_commit_marks() 会立即停止。

这发生在使用遥控器进行“git fetch”通话期间。 fetch 协商将远程 refs 与本地 refs 进行比较,并将一些提交标记为UNINTERESTING。

我测试了运行 clear_commit_marks_many() 以清除 close_reachable() 中的 UNINTERESTING 标志,但提示没有标志,所以什么也没做。

原来calculate_changed_submodule_paths() 方法有问题。谢谢,Peff,指出这个细节!更具体地说,对于每个子模块,collect_changed_submodules() 运行修订遍历以基本上对子模块列表进行文件历史记录。如果通过不更改子模块来简化它们,则该修订步行标记提交 UNININTERESTING。

相反,我最终得出结论,我应该使用代码的任何其他部分中未使用的标志。在commit-reach.c 中,为提交遍历算法定义了许多标志。 REACHABLE 标志似乎是最有意义的,它似乎并没有在文件中实际使用。
REACHABLE 标志在 commit-reach.c 的早期版本中使用,但被 4fbcca4 删除(“commit-reach: make can_all_from_reach...linear”, 2018-07-20, v2.20.0-rc0) .

将REACHABLE 标志添加到commit-graph.c 并在close_reachable() 中使用它而不是UNINTERESTING。
这修复了手动测试中的错误。


从多个远程并行提取到同一个存储库与最近更改(可选)在提取作业完成后更新提交图有不良交互,因为这些并行提取相互竞争。

这已在 Git 2.25(2020 年第一季度)中得到纠正。

参见Johannes Schindelin (dscho)commit 7d8e72b、commit c14e6e7(2019 年 11 月 3 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit bcb06e2,2019 年 12 月 1 日)

fetch:添加命令行选项--write-commit-graph

签字人:约翰内斯·辛德林

此选项会覆盖配置设置 fetch.writeCommitGraph,如果两者都已设置。

还有:

fetch:避免 fetch.jobs/fetch.writeCommitGraph 之间的锁定问题

签字人:约翰内斯·辛德林

当fetch.jobs 和fetch.writeCommitGraph 都设置了时,我们目前尝试在每个并发获取作业中编写提交图,这经常会导致这样的错误消息:

fatal: Unable to create '.../.git/objects/info/commit-graphs/commit-graph-chain.lock': File exists.

让我们通过推迟编写提交图直到所有获取作业完成来避免这种情况。


在获取用于拆分结果文件的参数的计算虚假值时编写拆分提交图文件的代码,已在 Git 2.25(2020 年第一季度)中更正。

参见commit 63020f1(2020 年 1 月 2 日)Derrick Stolee (derrickstolee)。
(由 Junio C Hamano -- gitster -- 合并到 commit 037f067,2020 年 1 月 6 日)

commit-graph:当给定零时首选默认size_mult

签字人:Derrick Stolee

在50f26bd ("fetch: add fetch.writeCommitGraph config setting", 2019-09-02, Git v2.24.0-rc0 -- merge 列在batch #4) 中,内置的 fetch 添加了使用“--split”功能编写提交图的能力。
此功能创建多个提交图文件,这些文件可以基于一组“拆分选项”进行合并,包括大小倍数。
默认大小倍数为 2,旨在提供提交图链的log_2N 深度,其中 N 是提交次数。

但是,我在 dogfooding 期间注意到,当仅由“git fetch”构建时,我的提交图链变得非常大。
事实证明,在split_graph_merge_strategy() 中,我们将size_mult 变量默认为2,除非我们用上下文的split_opts 覆盖它(如果它们存在)。
在builtin/fetch.c 中,我们创建了这样一个split_opts,,但不使用值填充它。

这个问题是由于两个故障造成的:

  1. 尚不清楚我们是否可以将标志 COMMIT_GRAPH_WRITE_SPLIT 添加到 NULL split_opts。
  2. 如果我们有一个非 NULL split_opts,,那么即使给出了零值,我们也会覆盖默认值。

纠正这两个问题。

  • 首先,在选项提供零值时,请勿覆盖@ 987654592。
  • 其次,停止在 fetch 内置函数中创建split_opts。

请注意,在使用 magic pathspec 时,git log 在 Git 2.22(2019 年 5 月)和 Git 2.27(2020 年第二季度)之间被破坏。

“git log :/a/b/”的命令行解析被破坏了大约整整一年,没有人注意到,已经更正了。

参见Jeff King (peff)commit 0220461(2020 年 4 月 10 日)。
请参阅 Junio C Hamano (gitster) 的 commit 5ff4b92(2020 年 4 月 10 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 95ca489,2020 年 4 月 22 日)

sha1-name: 不要假设 ref store 已经初始化了

报告人:Érico Rolim

c931ba4e ("sha1-name.c``: remove the_repo from handle_one_ref()", 2019-04-16, Git v2.22.0-rc0 -- merge 列在batch #8 ) 将for_each_ref() 助手的使用替换为refs_for_each_ref(),它可以与默认存储库实例的主引用存储一起使用,它可以在任何引用存储实例上工作,假设给定函数的存储库实例具有它的引用存储已初始化。

但可能没有人对其进行初始化,在这种情况下,代码最终会取消引用 NULL 指针。

还有:

repository:将“refs”指针标记为私有

签字人:杰夫·金

结构存储库中的“refs”指针以NULL 开头,但在通过get_main_ref_store() 访问时会延迟初始化。
然而,调用代码很容易忘记这一点并直接访问它,导致代码在某些时间可以工作,但如果在其他人访问 refs 之前调用它就会失败。

这是由5ff4b920eb 修复的错误的原因(“sha1-name:不要假设引用存储已初始化”,2020-04-09,Git v2.27.0 -- merge 在@ 中列出987654444@)。为了防止出现类似的bug,让我们更清楚地将“refs”字段标记为私有。

【讨论】:

  • 如果你使用 git 2.29 或更高版本,你应该运行git commit-graph write --reachable --changed-paths。这将预先计算文件路径,因此作用域为文件的git log 命令也将从该缓存中受益。
  • @T4cC0re 同意。我在stackoverflow.com/a/38788417/6309 中提到了可达性。我已将您的评论包含在答案中以提高知名度。
【解决方案2】:

您是对的,生成 56'000 次提交的报告生成 224'000 行 (15MiB) 的输出确实需要 20 到 35 秒。我实际上认为这是相当不错的表现,但你没有;好的。

由于您使用不变的数据库中的常量格式生成报告,因此您只需执行一次。之后可以使用git log的缓存结果,跳过耗时的生成。例如:

git log --pretty=format:%H\t%ae\t%an\t%at\t%s --numstat > log-pretty.txt

您可能想知道在整个报告中搜索感兴趣的数据需要多长时间。这是一个有价值的问题:

$ tail -1 log-pretty.txt
30  0   railties/test/webrick_dispatcher_test.rb
$ time grep railties/test/webrick_dispatcher_test.rb log-pretty.txt 
…
30  0   railties/test/webrick_dispatcher_test.rb

real    0m0.012s
…

不错,“缓存”的引入已将所需时间从 35 多秒减少到十几毫秒。这几乎快 3000 倍。

【讨论】:

  • 没有考虑缓存,这个很完美!
【解决方案3】:

我的第一个想法是改进您的 IO,但我使用 SSD 对 rails 存储库进行了测试,得到了类似的结果:30 秒。

--numstat 是让一切变慢的原因,否则git-log 可以在 1 秒内完成,即使格式化。做一个差异是昂贵的,所以如果你能从你的过程中删除它,那将大大加快速度。也许事后才这样做。

否则,如果您使用git-log 自己的搜索工具过滤日志条目,这将减少需要进行比较的条目数量。例如,git log --grep=foo --numstat 只需一秒钟。They're in the docs under "Commit Limiting"。这可以大大减少 git 必须格式化的条目数量。修订范围、日期过滤器、作者过滤器、日志消息 grepping...所有这些都可以提高 git-log 在大型存储库上的性能,同时执行昂贵的操作。

【讨论】:

    【解决方案4】:

    还有另一种提高git log 性能的途径,它建立在in the previous answer 提到的提交图之上。

    Git 2.27(2020 年第二季度)引入了对提交图的扩展,以便更高效地使用 Bloom filters 检查每次提交时修改的路径.

    请参阅commit caf388c(2020 年 4 月 9 日)和Derrick Stolee (derrickstolee)commit e369698(2020 年 3 月 30 日)。
    请参阅commit d5b873c、commit a759bfa、commit 42e50e7、commit a56b946、commit d38e07b、commit 1217c03、commit 76ffbca(2020 年 4 月 6 日)和commit 3d11275、@9876543334@、@9876543334@、@9876533@987654 ,commit f52207a,commit 3be7efc(2020 年 3 月 30 日)Garima Singh (singhgarima)。
    请参阅 Jeff King (peff) 的 commit d21ee7d(2020 年 3 月 30 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit 9b6606f,2020 年 5 月 1 日)

    revision.c:使用布隆过滤器加快基于路径的修订遍历

    帮助者:Derrick Stolee
    帮助者:SZEDER Gábor
    帮助者:Jonathan Tan
    签字人:Garima Singh

    如果提交图文件中存在特定路径(用于计算该路径的历史记录),修订遍历现在将使用 Bloom 过滤器进行提交以加快修订遍历。

    我们在 prepare_revision_walk 步骤中加载 Bloom 过滤器,目前仅在处理单个路径规范时。
    将来可以在本系列的基础上探索和构建将其扩展为使用多个路径规范。

    在比较 rev_compare_trees() 中的树时,如果 Bloom 过滤器表明文件在两棵树之间没有不同,我们不需要计算代价高昂的差异。
    这就是我们获得性能提升的地方。

    Bloom 过滤器的另一个响应是 '`:maybe',在这种情况下,我们会回退到完整的 diff 计算来确定路径是否在提交中更改。

    当指定了“--walk-reflogs”选项时,我们不会尝试使用布隆过滤器。
    '--walk-reflogs' 选项不像其他选项那样遍历提交祖先链。
    在遍历 reflog 条目时加入性能提升会增加更多复杂性,可以在以后的系列中进行探讨。

    性能提升:我们在 git repo、linux 和一些内部大型 repo 上测试了 git log -- &lt;path&gt; 的性能,具有不同深度的各种路径。

    在 git 和 linux 存储库上:

    • 我们观察到速度提高了 2 到 5 倍。

    在一个大型内部存储库中,文件位于树的 6-10 层深处:

    • 我们观察到速度提高了 10 到 20 倍,有些路径的速度提高了 28 倍。

    但是:修复(使用 Git 2.27,2020 年第二季度)模糊器发现的漏洞。

    参见 Jonathan Tan (jhowtan) 的 commit fbda77c(2020 年 5 月 4 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit 95875e0,2020 年 5 月 8 日)

    commit-graph:避免内存泄漏

    签字人:Jonathan Tan
    审核人:Derrick Stolee

    在fuzz-commit-graph.c 提供的入口点上运行的模糊器在parse_commit_graph() 创建结构bloom_filter_settings 然后由于错误而提前返回时发现内存泄漏。

    在由于错误而提前返回之前始终先释放该结构(如果存在)来修复该错误。

    在进行更改时,我还注意到另一个可能的内存泄漏 - 当提供了 BLOOMDATA 块但未提供 BLOOMINDEXES 时。
    同时修复该错误。


    Git 2.27(2020 年第二季度)再次改进了布隆过滤器:

    见commit b928e48(2020 年 5 月 11 日)SZEDER Gábor (szeder)。
    请参阅commit 2f6775f、commit 65c1a28、commit 8809328、commit 891c17c(2020 年 5 月 11 日)和commit 54c337b、commit eb591e4(2020 年 5 月 1 日)Derrick Stolee (derrickstolee)。
    (由 @ 合并987654360@commit 4b1e5e5,2020 年 5 月 14 日)

    bloom:删除重复的目录条目

    签字人:Derrick Stolee

    在计算更改路径的布隆过滤器时,我们需要从差异计算中获取更改的文件并提取父目录。这样,诸如“Documentation”之类的目录路径规范可以匹配更改“Documentation/git.txt”的提交。

    但是,当前的代码在这个过程中做得很差。

    路径被添加到哈希图中,但我们不检查该路径是否已经存在条目。
    这可能会创建许多重复条目,并导致过滤器的长度比应有的长得多。
    这意味着过滤器比预期的要稀疏,这有助于提高误报率,但会浪费大量空间。

    在hashmap_add()之前正确使用hashmap_get()。
    还要确保包含比较功能,以便正确匹配。

    这会影响t0095-bloom.sh 中的测试。
    这是有道理的,“smallDir”内部有十个变化,所以过滤器中的路径总数应该是 11。
    这将导致需要 11 * 10 位,而每个字节 8 位,这将导致 14 个字节。


    在 Git 2.28(2020 年第三季度)中,“git log -L...”现在利用“此提交触及哪些路径?”存储在提交图系统中的信息。

    为此,使用了布隆过滤器。

    见commit f32dde8(2020 年 5 月 11 日)Derrick Stolee (derrickstolee)。
    请参阅SZEDER Gábor (szeder) 的commit 002933f、commit 3cb9d2b、commit 48da94b、commit d554672(2020 年 5 月 11 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit c3a0282,2020 年 6 月 9 日)

    line-log:与 changed-path Bloom 过滤器集成

    签字人:Derrick Stolee

    之前对 line-log 机制的更改侧重于使第一个结果显示得更快。这是通过在返回早期结果之前不再遍历整个提交历史来实现的。
    还有另一种提高性能的方法:更快地遍历大多数提交。让我们使用改变路径的布隆过滤器来减少计算差异所花费的时间。

    由于 line-log 计算需要打开 blob 并检查 content-diff,因此仍有许多必要的计算无法用更改路径的 Bloom 过滤器替换。
    我们可以减少的部分在检查多个目录深处的文件的历史记录并且这些目录经常被修改时最有效。
    在这种情况下,检查提交是否为TREESAME 到其第一个父级的计算需要很大一部分时间。
    使用更改路径布隆过滤器进行改进的时机已经成熟。

    我们必须确保在revision.c 中调用prepare_to_use_bloom_filters(),以便将bloom_filter_settings 从提交图加载到结构rev_info。
    当然,某些情况下仍然是被禁止的,但在line-log 情况下,路径规范的提供方式与正常情况不同。

    由于可以请求多个路径和段,我们在提交遍历期间动态计算结构 bloom_key 数据。这可能会得到改进,但会增加目前没有价值的代码复杂性。

    有两种情况需要关注:合并提交和“普通”提交。

    • 合并提交有多个父级,但如果我们对每个范围内的第一个父级都是 TREESAME,则将所有范围的责任转嫁给第一个父级。
    • 普通提交具有相同的条件,但在 process_ranges_[merge|ordinary]_commit() 方法中的完成方式略有不同。

    通过检查更改路径布隆过滤器是否可以保证 TREESAME,我们可以避免树差异成本。如果过滤器显示“可能已更改”,那么我们需要运行 tree-diff 和 blob-diff(如果有真正的编辑)。

    Linux 内核存储库是此处声称的性能改进的良好试验场。
    有两种不同的情况需要测试:

    • 第一个是“整个历史记录”案例,我们将整个历史记录输出到 /dev/null,以查看计算完整的行日志历史记录需要多长时间。
    • 第二个是“第一个结果”的情况,我们发现显示第一个值需要多长时间,这是一个指示用户在终端等待时看到响应的速度。

    为了测试,我使用以下命令 (stolen from StackOverflow) 选择了前 10,000 个提交中更改最频繁的路径:

    git log --pretty=format: --name-only -n 10000 | sort | \
      uniq -c | sort -rg | head -10
    

    导致

    121 MAINTAINERS
     63 fs/namei.c
     60 arch/x86/kvm/cpuid.c
     59 fs/io_uring.c
     58 arch/x86/kvm/vmx/vmx.c
     51 arch/x86/kvm/x86.c
     45 arch/x86/kvm/svm.c
     42 fs/btrfs/disk-io.c
     42 Documentation/scsi/index.rst
    

    (以及虚假的第一个结果)。
    似乎路径 arch/x86/kvm/svm.c 已重命名,因此我们忽略该条目。这为实际命令时间留下了以下结果:

    |                              | Entire History  | First Result    |
    | Path                         | Before | After  | Before | After  |
    |------------------------------|--------|--------|--------|--------|
    | MAINTAINERS                  | 4.26 s | 3.87 s | 0.41 s | 0.39 s |
    | fs/namei.c                   | 1.99 s | 0.99 s | 0.42 s | 0.21 s |
    | arch/x86/kvm/cpuid.c         | 5.28 s | 1.12 s | 0.16 s | 0.09 s |
    | fs/io_uring.c                | 4.34 s | 0.99 s | 0.94 s | 0.27 s |
    | arch/x86/kvm/vmx/vmx.c       | 5.01 s | 1.34 s | 0.21 s | 0.12 s |
    | arch/x86/kvm/x86.c           | 2.24 s | 1.18 s | 0.21 s | 0.14 s |
    | fs/btrfs/disk-io.c           | 1.82 s | 1.01 s | 0.06 s | 0.05 s |
    | Documentation/scsi/index.rst | 3.30 s | 0.89 s | 1.46 s | 0.03 s |
    

    值得注意的是,MAINTAINERS 文件的加速最少:

    • 经常编辑,
    • 在目录层次结构中处于低位,并且
    • 相当大的文件。

    所有这些点都会导致花更多时间进行 blob 差异,而花费更少的时间进行树差异。
    尽管如此,我们还是看到了这种情况的一些改进,而其他情况的显着改进。
    2-4 倍的加速可能是更典型的情况,而不是该文件的 5% 的小幅变化。


    在 Git 2.29(2020 年第 4 季度)中,改变路径的布隆过滤器使用来自独立实施的想法进行了改进。

    见commit 7fbfe07,commit bb4d60e,commit 5cfa438,commit 2ad4f1a,commit fa79653,commit 0ee3cb8,commit 1df15f8,commit 6141cdf,commit 6141cdf,commit cb9daf1,commit 35a9f1e@@(05) 987654385@.
    (由 Junio C Hamano -- gitster -- 合并于 commit de6dda0,2020 年 7 月 30 日)

    commit-graph:简化parse_commit_graph() #1

    签字人:SZEDER Gábor
    签字人:Derrick Stolee

    当我们遍历块查找表的所有条目时,我们确保我们不会尝试读取超出 mmap-ed 提交图文件的末尾,并在每次迭代中检查我们的块 ID 和偏移量are about to read 仍在 mmap-ed 内存区域内。但是,每次迭代中的这些检查并不是真正必要的,因为在此循环之前,提交图文件中的块数已经从刚刚解析的提交图标头中得知。

    因此,在我们开始迭代这些条目之前,让我们检查提交图文件是否足够大以容纳块查找表中的所有条目,并删除这些每次迭代检查。
    在此期间,请考虑拥有有效提交图文件所需的所有内容的大小,即标头的大小、强制 OID 扇出块的大小以及预告片中签名的大小.

    请注意,这也需要更改错误消息。se

    还有commit-graph:

    块查找表在提交图文件中存储块的起始偏移量,而不是它们的大小。
    因此,块的大小只能通过从后续块的偏移量(或终止标签的偏移量)中减去其偏移量来计算。
    这目前以一种有点复杂的方式实现:当我们遍历块查找表的条目时,我们检查每个块的 id 并存储它的起始偏移量,然后我们检查最后看到的块的 id 并使用计算它的大小它以前保存的偏移量。
    目前我们计算它的大小只有一个块,但是这个补丁系列会增加更多,重复的块 id 检查不是那么漂亮。

    相反,让我们在每次迭代时提前读取下一个块的偏移量,这样我们就可以立即计算每个块的大小,就在我们存储其起始偏移量的位置。

    【讨论】:

      猜你喜欢
      • 2013-10-14
      • 2016-07-19
      • 1970-01-01
      • 2017-02-23
      • 2011-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-05
      相关资源
      最近更新 更多