如何提高git日志的性能?

16
我正在尝试从几个仓库中提取Git日志,例如这样:

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

对于像rails/rails这样的大型存储库,生成日志需要花费35秒以上的时间。

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


1
尝试使用 --max-count=30,如 git-log文档 中所述。你真的需要查看Rails项目的所有56,000个提交吗? - msw
对于这个项目, @msw 很不幸地是的。 - George L
Git 2.18(Q2 2018)应该会大大提高git log的性能。请参见下面的答案 - VonC
4个回答

26

简而言之,正如在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评论中提到的:

If you are on git version 2.29 or above you should rather run:

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

This will pre-compute file paths, so that git log commands that are scoped to files also benefit from this cache.


Git 2.18(2018年第二季度)将改善git log性能:

查看提交902f5a2(2018年3月24日)由René Scharfe (rscharfe)提交。
查看提交0aaf05b提交3d475f4(2018年3月22日)由Derrick Stolee (derrickstolee)提交。
查看提交626fd98(2018年3月22日)由brian m. carlson (bk2204)提交。
(由Junio C Hamano -- gitster --合并于提交51f813c,2018年4月10日)

sha1_name: 使用bsearch_pack()进行缩写

When computing abbreviation lengths for an object ID against a single packfile, the method find_abbrev_len_for_pack() currently implements binary search.
This is one of several implementations.
One issue with this implementation is that it ignores the fanout table in the pack-index.

Translate this binary search to use the existing bsearch_pack() method that correctly uses a fanout table.

Due to the use of the fanout table, the abbreviation computation is slightly faster than before.

For a fully-repacked copy of the Linux repo, the following 'git log' commands improved:

* 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, commit 08fd81c, commit 4ce58ee, commit ae30d7b, commit b84f767, commit cfe8321, commit f2af9f5 (2018年4月2日) 由 Derrick Stolee (derrickstolee) 提交的代码。
(由 Junio C Hamano -- gitster -- 合并于 commit b10edb2,2018年5月8日)

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

Teach Git to inspect a commit graph file to supply the contents of a struct commit when calling parse_commit_gently().
This implementation satisfies all post-conditions on the struct commit, including loading parents, the root tree, and the commit date.

If core.commitGraph is false, then do not check graph files.

In test script t5318-commit-graph.sh, add output-matching conditions on read-only graph operations.

By loading commits from the graph instead of parsing commit buffers, we save a lot of time on long commit walks.

Here are some performance results for a copy of the Linux repository where 'master' has 678,653 reachable commits and is behind 'origin/master' by 59,929 commits.

| 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%  |
了解有关提交图的更多信息,请参阅 " 'git log --graph'如何工作?"。

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

代码已经学会使用存储在提交图文件中的重复信息,以了解提交的树对象名称,从而避免在有意义时打开和解析提交对象。

查看 commit 279ffad(2018年4月30日)由SZEDER Gábor (szeder)提交。
查看 commit 7b8a21d, commit 2e27bd7, commit 5bb03de, commit 891435d(2018年4月6日)由Derrick Stolee (derrickstolee)提交。
(由Junio C Hamano -- gitster --合并于commit c89b6e1,2018年5月23日)

commit-graph: 为提交懒加载树

The commit-graph file provides quick access to commit data, including the OID of the root tree for each commit in the graph. When performing a deep commit-graph walk, we may not need to load most of the trees for these commits.

Delay loading the tree object for a commit loaded from the graph until requested via get_commit_tree().
Do not lazy-load trees for commits not in the graph, since that requires duplicate parsing and the relative peformance improvement when trees are not needed is small.

On the Linux repository, performance tests were run for the following command:

git log --graph --oneline -1000

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

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

请查看提交 8be88db(2019年1月7日),以及提交 4cea1ce提交 d4e19e5提交 0000d65(2019年1月6日),由René Scharfe (rscharfe)提交。
(由Junio C Hamano -- gitster --合并于提交 eb8638a,2019年1月18日)

对象存储:为松散缓存使用每个子目录一个oid_array

The loose objects cache is filled one subdirectory at a time as needed.
It is stored in an oid_array, which has to be resorted after each add operation.
So when querying a wide range of objects, the partially filled array needs to be resorted up to 255 times, which takes over 100 times longer than sorting once.

Use one oid_array for each subdirectory.
This ensures that entries have to only be sorted a single time. It also avoids eight binary search steps for each cache lookup as a small bonus.

The cache is used for collision checks for the log placeholders %h, %t and %p, and we can see the change speeding them up in a repository with ca. 100 objects per subdirectory:

$ 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月)在使用从提交图文件读取的数据之前检查错误。

请查看 提交 93b4405, 提交 43d3561, 提交 7b8ce9c, 提交 67a530f, 提交 61df89c, 提交 2ac138d (2019年3月25日),以及提交 945944c, 提交 f6761fa (2019年2月21日) 由Ævar Arnfjörð Bjarmason (avar)提交。
(由Junio C Hamano -- gitster --合并于提交 a5e4be2,2019年4月25日)

commit-graph 写入:如果现有的图形损坏,请勿崩溃

当写入commit-graph时,我们最终会调用parse_commit()。这将反过来调用代码来查看关于提交的现有commit-graph,如果图形损坏,我们就死了。
因此,如果设置了core.commitGraph=true,则无法对失败的"commit-graph verify"进行后续处理,并且需要手动删除该图才能继续,或者将core.commitGraph设置为"false"。
更改"commit-graph write"代码路径,使用新的parse_commit_no_graph()助手而不是parse_commit()以避免出现这种情况。后者将在177722b("commit: integrate commit graph with commit parsing", 2018-04-10, Git v2.18.0-rc0)中看到带有use_commit_graph=1repo_parse_commit_internal()
完全不使用旧图会略微减慢写入新图的速度,但这是一种合理的方式,可以防止现有提交图中的错误扩散。

从 Git 2.24+(2019 年第三季度)开始,默认情况下启用了提交图

查看 提交 aaf633c, 提交 c6cc4c5, 提交 ad0fb65, 提交 31b1de6, 提交 b068d9a, 提交 7211b9e (2019年8月13日) 由 Derrick Stolee (derrickstolee) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 f4f8dfe, 2019年9月9日)

commit-graph: 默认开启提交图

自从引入以来,过去一年左右的时间里,提交图(commit-graph)功能已经得到了很多活动。
该功能是中型到大型仓库的关键性能增强,对于小型仓库没有显著影响。

core.commitGraphgc.writeCommitGraph 的默认值更改为 true,以便用户默认受益于此功能

评论所述,由guildenstern

目前(Git 2.40.0,2023年第一季度),即使将fetch.writeCommitGraph设置为true,在git clone时提交图形也不会被更新。请参阅最近的讨论(2023年4月)

在Git 2.24(2019年第4季度)中,一个配置变量告诉“git fetch”在完成后写入提交图。

请参见提交50f26bd(2019年9月3日),作者是Derrick Stolee(derrickstolee
(由Junio C Hamano -- gitster --提交5a53509中合并,2019年9月30日)

fetch:添加fetch.writeCommitGraph配置设置

现在默认情况下启用了提交图功能,并且默认情况下在“git gc”期间进行编写。通常,Git仅在“git gc --auto”命令传递gc.auto设置以实际执行工作时才编写提交图。这意味着提交图通常会落后于每天使用的提交。
为了保持最新提交的更新,请在获取新对象后添加一个步骤到“git fetch”中以编写提交图。fetch.writeCommitGraph配置设置启用编写分割提交图,因此平均编写此文件的成本非常小。偶尔,提交图链将折叠为单个级别,对于非常大的存储库来说可能会很慢。
对于其他用途,请在启用feature.experimental时将默认值调整为true。

在 Git 2.24 版本(2019年第四季度)中,commit-graph 功能更加健壮。

请参见 提交 6abada1提交 fbab552(2019年9月12日),由 Jeff King (peff) 创建。
(由 Junio C Hamano -- gitster -- 合并于 提交 098e8c6,2019年10月7日)

commit-graph: 将 DIE_ON_LOAD 检查提升到实际的加载时间

Commit 43d3561 (commit-graph write: don't die if the existing graph is corrupt, 2019-03-25, Git v2.22.0-rc0) added an environment variable we use only in the test suite, $GIT_TEST_COMMIT_GRAPH_DIE_ON_LOAD.
But it put the check for this variable at the very top of prepare_commit_graph(), which is called every time we want to use the commit graph.
Most importantly, it comes before we check the fast-path "did we already try to load?", meaning we end up calling getenv() for every single use of the commit graph, rather than just when we load.

getenv() is allowed to have unexpected side effects, but that shouldn't be a problem here; we're lazy-loading the graph so it's clear that at least one invocation of this function is going to call it.

But it is inefficient. getenv() typically has to do a linear search through the environment space.

We could memoize the call, but it's simpler still to just bump the check down to the actual loading step. That's fine for our sole user in t5318, and produces this minor real-world speedup:

[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年第四季度)还包括一个回归修复。

请参见提交cb99a34提交e88aab9(2019年10月24日),作者为Derrick Stolee(derrickstolee
(由Junio C Hamano -- gitster --提交dac1d83中合并,2019年11月4日)

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

报告者:Johannes Schindelin
协助者:Jeff King
协助者:Szeder Gábor
签名作者:Derrick Stolee

The previous commit includes a failing test for an issue around fetch.writeCommitGraph and fetching in a repo with a submodule. Here, we fix that bug and set the test to "test_expect_success".

The problem arises with this set of commands when the remote repo at <url> has a submodule.
Note that --recurse-submodules is not needed to demonstrate the bug.

$ 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)

As an initial fix, I converted the code in builtin/fetch.c that calls write_commit_graph_reachable() to instead launch a "git commit-graph write --reachable --split" process. That code worked, but is not how we want the feature to work long-term.

That test did demonstrate that the issue must be something to do with internal state of the 'git fetch' process.

The write_commit_graph() method in commit-graph.c ensures the commits we plan to write are "closed under reachability" using close_reachable().
This method walks from the input commits, and uses the UNINTERESTING flag to mark which commits have already been visited. This allows the walk to take O(N) time, where N is the number of commits, instead of O(P) time, where P is the number of paths. (The number of paths can be exponential in the number of commits.)

However, the UNINTERESTING flag is used in lots of places in the codebase. This flag usually means some barrier to stop a commit walk, such as in revision-walking to compare histories.
It is not often cleared after the walk completes because the starting points of those walks do not have the UNINTERESTING flag, and clear_commit_marks() would stop immediately.

This is happening during a 'git fetch' call with a remote. The fetch negotiation is comparing the remote refs with the local refs and marking some commits as UNINTERESTING.

I tested running clear_commit_marks_many() to clear the UNINTERESTING flag inside close_reachable(), but the tips did not have the flag, so that did nothing.

It turns out that the calculate_changed_submodule_paths() method is at fault. Thanks, Peff, for pointing out this detail! More specifically, for each submodule, the collect_changed_submodules() runs a revision walk to essentially do file-history on the list of submodules. That revision walk marks commits UNININTERESTING if they are simplified away by not changing the submodule.

Instead, I finally arrived on the conclusion that I should use a flag that is not used in any other part of the code. In commit-reach.c, a number of flags were defined for commit walk algorithms. The REACHABLE flag seemed like it made the most sense, and it seems it was not actually used in the file.
The REACHABLE flag was used in early versions of commit-reach.c, but was removed by 4fbcca4 ("commit-reach: make can_all_from_reach... linear", 2018-07-20, v2.20.0-rc0).

Add the REACHABLE flag to commit-graph.c and use it instead of UNINTERESTING in close_reachable().
This fixes the bug in manual testing.


从多个远程仓库并行地获取到同一个仓库中,与最近的更改(可选地)在获取作业完成后更新提交图表产生了不良互动,因为这些并行获取彼此竞争。

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

请查看提交记录7d8e72b提交记录c14e6e7(2019年11月3日)作者为 Johannes Schindelin (dscho)
(由Junio C Hamano -- gitster --合并于提交记录bcb06e2, 2019年12月1日)

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

签名作者:Johannes Schindelin

如果两者都设置了,此选项将覆盖配置设置fetch.writeCommitGraph

并且:

fetch:避免在fetch.jobs/fetch.writeCommitGraph之间出现锁定问题

签名作者:Johannes Schindelin

When both fetch.jobs and fetch.writeCommitGraph is set, we currently try to write the commit graph in each of the concurrent fetch jobs, which frequently leads to error messages like this one:

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

Let's avoid this by holding off from writing the commit graph until all fetch jobs are done.


在 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年9月2日,Git v2.24.0-rc0 - merge列在batch #4中),fetch内置程序添加了使用“--split”功能编写提交图的功能。此功能创建多个提交图文件,并且可以根据一组“拆分选项”进行合并,包括大小倍数。默认大小倍数为2,旨在提供一个log_2 N深度的提交图链,其中N是提交数量。
然而,我在使用'git fetch'构建时注意到我的提交图链变得非常大。原来,在split_graph_merge_strategy()中,我们将size_mult变量默认设置为2,除非存在上下文的split_opts,否则我们将其覆盖。在builtin/fetch.c中,我们创建了这样的split_opts,但没有用值填充它。
这个问题由两个失败引起:
1.不清楚我们是否可以使用NULLsplit_opts添加标志COMMIT_GRAPH_WRITE_SPLIT
2.如果我们有一个非空的split_opts,那么即使给出零值,我们也会覆盖默认值。
纠正这两个问题。
首先,在选项提供零值时不要覆盖size_mult
其次,停止在fetch内置程序中创建split_opts
请注意,使用魔术路径时,在Git 2.22(2019年5月)和Git 2.27(2020年第二季度)之间的时间内使用git log命令可能会出现问题。
关于 "git log :/a/b/" 的命令行解析在一整年内都存在问题而没有任何人察觉到,这个问题已经得到了修复。

请查看 提交 0220461(2020年4月10日)由 Jeff King (peff) 提交。
请查看 提交 5ff4b92(2020年4月10日)由 Junio C Hamano (gitster) 提交。
(由 Junio C Hamano -- gitster --提交 95ca489 中合并,日期为2020年4月22日)

sha1-name:不要假设引用存储已初始化

报告人:Érico Rolim

c931ba4e(“sha1-name.c``:从handle_one_ref()中删除the_repo”,2019年4月16日,Git v2.22.0-rc0--在批处理#8中列出的合并)替换了使用for_each_ref()辅助工具的方式,该工具与默认存储库实例的主引用存储一起工作,而是使用refs_for_each_ref(),它可以在任何引用存储实例上工作,因为假定传递给函数的存储库实例已经初始化其引用存储。

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

而且:

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

签名作者:Jeff King

struct repository中的“refs”指针最初是NULL,但当通过get_main_ref_store()访问时,它会被延迟初始化。然而,调用代码很容易忘记这一点,并直接访问它,导致代码有时可以工作,但如果在其他人访问refs之前调用它,则会失败。这是由5ff4b920eb修复的错误(“sha1-name:不要假设ref store已初始化”,2020-04-09,Git v2.27.0 - 在batch#3中列出的merge)。为了防止类似的错误,让我们更明确地将“refs”字段标记为私有。

2
如果您使用的是 Git 2.29 或更高版本,则最好运行 git commit-graph write --reachable --changed-paths。这将预先计算文件路径,因此针对文件进行作用域限制的 git log 命令也可以从此缓存中受益。 - T4cC0re
1
@T4cC0re同意。我在 https://dev59.com/wmEh5IYBdhLWcg3wRBj5#38788417 中提到了“reachable”。我已将您的评论包含在答案中以增加曝光度。 - VonC
请注意,目前(Git 2.40.0),即使将fetch.writeCommitGraph设置为true,在进行 git clone时提交图不会被更新。详见这个最近的讨论 - Guildenstern
1
@Guildenstern 很好的观点,谢谢您的反馈。我已经将您的评论包含在答案中以增加可见性。 - VonC

5

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

--numstat 是导致一切放慢的东西,否则 git-log 可以在格式化的情况下在1秒内完成。做差异很昂贵,因此如果您可以从流程中删除它,那么这将极大地加快速度。也许可以事后再做。

否则,如果您使用git-log自己的搜索工具来过滤日志条目,那么这将减少需要执行差异的条目数量。例如:git log --grep=foo --numstat只需一秒钟。它们在“提交限制”下的文档中有说明。这可以极大地减少git必须格式化的条目数。修订范围、日期过滤器、作者过滤器、日志消息 grepping...所有这些都可以在进行昂贵的操作时提高git-log在大型存储库上的性能。


3

您说得没错,对于生成包含 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倍。


没有考虑缓存,这太完美了! - George L

1

有另外一种方法可以提高git log的性能,它建立在之前回答中提到的提交图基础上。

Git 2.27(2020年第二季度)引入了一个扩展提交图,使用Bloom过滤器有效地检查每个提交时修改的路径。

请查看caf388c提交(2020年4月9日)和e369698提交(2020年3月30日),作者为Derrick Stolee(derrickstolee
请查看d5b873c提交a759bfa提交42e50e7提交a56b946提交d38e07b提交1217c03提交76ffbca提交(2020年4月6日)以及3d11275提交f97b932提交ed591fe提交f1294ea提交f52207a提交3be7efc提交(2020年3月30日),作者为Garima Singh(singhgarima
请查看d21ee7d提交(2020年3月30日),作者为Jeff King(peff
(由Junio C Hamano -- gitster --commit 9b6606f中合并)

revision.c:使用布隆过滤器加速基于路径的版本遍历

协助:Derrick Stolee <dstolee@microsoft.com
协助:SZEDER Gábor
协助:Jonathan Tan
签名:Garima Singh

修订遍历现在将使用Bloom过滤器来加速特定路径的修订遍历(计算该路径的历史记录),如果它们存在于提交图文件中。我们在“prepare_revision_walk”步骤中加载Bloom过滤器,目前仅在处理单个路径规范时使用。将其扩展到使用多个路径规范可以在未来探索并构建在此系列的基础上。在“rev_compare_trees()”中比较树时,如果Bloom过滤器表示两个树之间的文件没有差异,我们就不需要计算昂贵的差异。这是我们获得性能提升的地方。Bloom过滤器的另一个响应是“:maybe”,在这种情况下,我们回退到完整的差异计算以确定路径是否在提交中更改。当指定“--walk-reflogs”选项时,我们不尝试使用Bloom过滤器。“--walk-reflogs”选项不像其他选项一样遍历提交祖先链。在遍历reflog条目时合并性能增益会增加更多的复杂性,可以在以后的系列中探讨。 性能提升:我们在git repo、linux以及一些内部大型repo上测试了git log -- <path>的性能,使用了不同深度和路径的多种情况。在git和linux repo上,我们观察到了2倍至5倍的速度提升。在一个庞大的内部repo中,有些文件位于树形结构中的第6-10级目录下,我们观察到了10倍至20倍的速度提升,有些路径的速度甚至提高了28倍。

注意:Git 2.39(2022年第四季度)将现有的模糊测试相关源代码移动到它们自己的 oss-fuzz/ 层次结构中。

请参见 提交 6713bfc(2022年9月19日),由Arthur Chan(arthurscchan撰写。
(由Junio C Hamano -- gitster --提交9b89c08中合并,2022年10月7日)


但是:修复了由模糊测试器发现的泄漏(使用Git 2.27,2020年第二季度)。请参见提交fbda77c(由Jonathan Tan (jhowtan)于2020年5月4日提交)。
(由Junio C Hamano -- gitster --合并于提交95875e0,2020年5月8日)

commit-graph:避免内存泄漏

Signed-off-by: Jonathan Tan
Reviewed-by: Derrick Stolee

一个在fuzz-commit-graph.c提供的入口点上运行的模糊测试器揭示了一个内存泄漏问题,当parse_commit_graph()创建一个结构体bloom_filter_settings并由于错误而提前返回时。

通过始终首先释放该结构体(如果存在),然后由于错误而提前返回来修复该错误。

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


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

请查看 提交 b928e48 (2020年5月11日)由 SZEDER Gábor(szeder 进行。
请查看 提交 2f6775f, 提交 65c1a28, 提交 8809328, 提交 891c17c (2020年5月11日),以及 提交 54c337b, 提交 eb591e4 (2020年5月1日)由 Derrick Stolee(derrickstolee 进行。
(由 Junio C Hamano -- gitster --提交 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...” 现在利用了存储在提交图系统中的“这个提交影响了哪些路径?”信息。为此,使用了布隆过滤器。

请参见 提交 f32dde8(2020年5月11日),作者为Derrick Stolee (derrickstolee)
请参见提交 002933f, 提交 3cb9d2b, 提交 48da94b, 提交 d554672(2020年5月11日),作者为SZEDER Gábor (szeder)
(由Junio C Hamano -- gitster --提交 c3a0282中合并)

line-log: 集成 changed-path 布隆过滤器

Signed-off-by: Derrick Stolee

The previous changes to the line-log machinery focused on making the first result appear faster. This was achieved by no longer walking the entire commit history before returning the early results.
There is still another way to improve the performance: walk most commits much faster. Let's use the changed-path Bloom filters to reduce time spent computing diffs.

Since the line-log computation requires opening blobs and checking the content-diff, there is still a lot of necessary computation that cannot be replaced with changed-path Bloom filters.
The part that we can reduce is most effective when checking the history of a file that is deep in several directories and those directories are modified frequently.
In this case, the computation to check if a commit is TREESAME to its first parent takes a large fraction of the time.
That is ripe for improvement with changed-path Bloom filters.

We must ensure that prepare_to_use_bloom_filters() is called in revision.c so that the bloom_filter_settings are loaded into the struct rev_info from the commit-graph.
Of course, some cases are still forbidden, but in the line-log case the pathspec is provided in a different way than normal.

Since multiple paths and segments could be requested, we compute the struct bloom_key data dynamically during the commit walk. This could likely be improved, but adds code complexity that is not valuable at this time.

There are two cases to care about: merge commits and "ordinary" commits.

  • Merge commits have multiple parents, but if we are TREESAME to our first parent in every range, then pass the blame for all ranges to the first parent.
  • Ordinary commits have the same condition, but each is done slightly differently in the process_ranges_[merge|ordinary]_commit() methods.

By checking if the changed-path Bloom filter can guarantee TREESAME, we can avoid that tree-diff cost. If the filter says "probably changed", then we need to run the tree-diff and then the blob-diff if there was a real edit.

The Linux kernel repository is a good testing ground for the performance improvements claimed here.
There are two different cases to test:

  • The first is the "entire history" case, where we output the entire history to /dev/null to see how long it would take to compute the full line-log history.
  • The second is the "first result" case, where we find how long it takes to show the first value, which is an indicator of how quickly a user would see responses when waiting at a terminal.

To test, I selected the paths that were changed most frequently in the top 10,000 commits using this command (stolen from StackOverflow):

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

which results in

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

(along with a bogus first result).
It appears that the path arch/x86/kvm/svm.c was renamed, so we ignore that entry. This leaves the following results for the real command time:

|                              | 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 |

It is worth noting that the least speedup comes for the MAINTAINERS file which is:

  • edited frequently,
  • low in the directory hierarchy, and
  • quite a large file.

All of those points lead to spending more time doing the blob diff and less time doing the tree diff.
Still, we see some improvement in that case and significant improvement in other cases.
A 2-4x speedup is likely the more typical case as opposed to the small 5% change for that file.


在 Git 2.29(2020年第四季度)中,改变路径布隆过滤器使用了从一个独立实现中得出的思想进行改进。

查看 提交 7fbfe07, 提交 bb4d60e, 提交 5cfa438, 提交 2ad4f1a, 提交 fa79653, 提交 0ee3cb8, 提交 1df15f8, 提交 6141cdf, 提交 cb9daf1, 提交 35a9f1e (2020年6月5日) 由SZEDER Gábor (szeder)进行。
(于2020年7月30日通过Junio C Hamano -- gitster --合并至提交 de6dda0)

commit-graph: 简化 parse_commit_graph() #1

由 SZEDER Gábor 签署
由 Derrick Stolee 签署

在迭代遍历块查找表中的所有条目时,我们确保不尝试读取超出mmap的提交图文件结尾,并在每次迭代中检查要读取的块ID和偏移量仍然在mmap的内存区域内。但是,这些每次迭代中的检查实际上并不必要,因为在此循环之前,从刚刚解析的提交图头部分已经知道了提交图文件中的块数。
因此,在我们开始迭代这些条目之前,请检查提交图文件是否足够大以容纳块查找表中的所有条目,并且放弃每次迭代的检查。顺便提一下,考虑到拥有有效提交图文件所需的一切大小,即标题的大小、强制OID扇区块的大小和拖车中签名的大小。
请注意,这还需要更改错误消息。
另外还有关于commit-graph内容
“块查找表”存储提交图文件中块的起始偏移量,而不是它们的大小。因此,块的大小只能通过从后续块(或终止标签)的偏移量减去其偏移量来计算。目前实现方式有点复杂:当我们迭代“块查找表”的条目时,我们检查每个块的ID并存储其起始偏移量,然后我们检查上一个已见块的ID并使用其先前保存的偏移量计算其大小。目前只有一个块需要计算其大小,但这个补丁系列将添加更多块,重复的块ID检查并不美观。相反,让我们在每次迭代时预读下一个块的偏移量,这样我们就可以立即计算每个块的大小,在存储其起始偏移量的地方进行计算。”

Git 2.38(2022年第三季度)带来了一个API调整,使得在提交图解析器上运行模糊测试更加容易。

请参见提交a92d852(2022年7月14日),由Taylor Blau (ttaylorr)提交。
(由Junio C Hamano -- gitster --提交36d7bd1中合并,2022年7月27日)

commit-graph: 传递 repo_settings 而不是仓库

签署者: Taylor Blau
签署者: Josh Steadmon

parse_commit_graph()函数需要一个'struct repository *'指针,但它只访问配置设置(直接或通过repo结构体的.settings字段)。将所有相关的配置设置移动到repo_settings结构体中,并更新parse_commit_graph()及其现有调用者,使其接受'struct repo_settings *'。

parse_commit_graph()的调用者现在需要自己调用prepare_repo_settings(),或直接初始化一个struct repo_settings

ab14d06(“commit-graph: pass a 'struct repository *' in more places”,2020-09-09,Git v2.29.0-rc0 -- merge listed in batch #18)之前,解析提交图是一个纯函数,仅依赖于提交图本身的内容。
提交ab14d06引入了对struct repository指针的依赖,后来的提交(例如b66d847(“commit-graph: respect 'commitGraph.readChangedPaths'”,2020-09-09,Git v2.29.0-rc0 -- merge listed in batch #18))增加了对配置设置的依赖,这些配置设置是通过存储库指针的settings字段访问的。
该字段是通过调用prepare_repo_settings()来初始化的。

此外,这还修复了fuzz-commit-graph中的一个问题:在44c7e62repo-settings: prepare_repo_settings only in git repos,2021-12-06,Git v2.35.0-rc0 -- merge listed in batch #4)(2021-12-06,repo-settings:prepare_repo_settings only in git repos)中,prepare_repo_settings被更改为如果由CWD不是Git存储库的进程调用,则发出BUG()。

上述提交的组合打破了fuzz-commit-graph,它试图将任意模糊引擎提供的字节解析为提交图文件。
在此更改之前,parse_commit_graph()调用了prepare_repo_settings(),但由于我们在没有有效存储库的情况下运行fuzz测试,因此我们对每个测试用例都会命中44c7e62中的BUG()。


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