如何提高git log的性能?

Geo*_*e L 3 git

我试图从这样的几个存储库中提取git日志:

git log --pretty=format:%H\t%ae\t%an\t%at\t%s --numstat
Run Code Online (Sandbox Code Playgroud)

对于较大的存储库(例如rails / rails),需要花费35秒钟以上的时间才能生成日志。

有没有办法改善这种性能?

Von*_*onC 7

TLDR;如GitMerge 2019中所述

git config --global core.commitGraph true
git config --global gc.writeCommitGraph true
cd /path/to/repo
git commit-graph write
Run Code Online (Sandbox Code Playgroud)

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


Git 2.18(Q2 2018)将改善git log性能:

提交902f5a2(2018年3月24日),由勒内Scharfe( )rscharfe
参见Derrick Stolee()的commit 0aaf05bcommit 3d475f4(2018年3月22日。 参见brian m。的commit 626fd98(2018年3月22日)卡尔森((由Junio C Hamano合并--commit 51f813c中,2018年4月10日)derrickstolee
bk2204
gitster

sha1_namebsearch_pack()用于缩写

在针对单个packfile计算对象ID的缩写长度时,该方法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%
Run Code Online (Sandbox Code Playgroud)

相同的Git 2.18添加了一个commits图:将计算祖先遍历所需的信息预先计算并存储在一个单独的文件中,以优化图遍历。

提交7547b95提交3d5df01提交049d51a提交177722b提交4f2542b提交1b70dfd提交2a2e32b(2018年4月10日),以及提交f237c8b提交08fd81c提交4ce58ee提交ae30d7b提交b84f767提交cfe8321提交f2af9f5(02 2018年4月)作者Derrick Stolee(derrickstolee
(由Junio C gitsterHamano合并--commit b10edb2中,2018年5月8日)

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

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

如果core.commitGraphfalse,则不检查图形文件。

在测试脚本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%  |
Run Code Online (Sandbox Code Playgroud)

要了解有关提交图的更多信息,请参见“ 如何git log --graph工作? ”。


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

该代码已被教导使用存储在提交图文件中的重复信息来学习提交的树对象名称,以避免在有意义的情况下打开和解析提交对象。

提交279ffad(2018年4月30日)由SZEDER伽柏(szeder
提交7b8a21d提交2e27bd7提交5bb03de提交891435d(2018年4月6日)由井架Stolee( )derrickstolee
(由Junio C gitsterHamano合并--commit c89b6e1中,2018年5月23日)

commit-graph:延迟加载树进行提交

commit-graph文件提供对提交数据的快速访问,包括图中每个提交的根树的OID。在执行深层提交图遍历时,我们可能不需要为这些提交加载大多数树。

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

在Linux系统信息库中,针对以下命令运行了性能测试:

git log --graph --oneline -1000

Before: 0.92s
After:  0.66s
Rel %: -28.3%
Run Code Online (Sandbox Code Playgroud)

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

提交8be88db(2019年1月7日),并提交4cea1ce提交d4e19e5提交0000d65(2019年1月6日),由勒内Scharfe( )rscharfe
(由Junio C gitsterHamano合并--eb8638a号提交中,2019年1月18日)

object-storeoid_array每个子目录使用一个缓存

松散对象缓存根据需要一次填充一个子目录。
它存储在中oid_array,必须在每次添加操作之后重新使用。
因此,当查询各种各样的对象时,部分填充的数组最多需要重新执行255次,这比排序一次要花费100多倍。

oid_array每个子目录使用一个。
这样可以确保只对条目进行一次排序。作为一个小小的奖励,它还避免了每个缓存查找的八个二进制搜索步骤。

缓存用于日志占位符%h%t和的冲突检查%p,我们可以看到更改使它们在带有ca的存储库中加快了速度。每个子目录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%
Run Code Online (Sandbox Code Playgroud)

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

提交93b4405提交43d3561提交7b8ce9c提交67a530f提交61df89c提交2ac138d(2019年3月25日),并提交945944c提交f6761fa(2019年2月21日)通过ÆvarArnfjörðBjarmason( )avar
(由Junio C gitsterHamano合并--commit a5e4be2中,2019年4月25日)

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

commit-graph被写入我们最终调用parse_commit()。反过来,这将调用代码,以咨询commit-graph有关提交的现有信息,如果图形损坏,我们将死亡。

因此,我们进入了以下状态:如果设置了失败的“ commit-graph verify”,则不能再加上“ commit-graph writecore.commitGraph=true,需要手动删除图形才能继续进行,或者core.commitGraph需要将其设置为“ false”。

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

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


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

请参阅Derrick Stolee(提交的commit aaf633ccommit c6cc4c5commit ad0fb65commit 31b1de6commit b068d9acommit 7211b9e(2019年8月13日(由Junio C Hamano合并--commit f4f8dfe中,2019年9月9日)derrickstolee
gitster

commit-graph:默认情况下启用提交图

自从引入以来,提交图功能在过去一年左右的时间里已经活跃了很多。
此功能是中型到大型仓库的关键性能增强,并且不会严重损害小型仓库。

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


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

参见Derrick Stolee()的commit 50f26bd(03 Sep 2019 (由Junio C Hamano合并--5a53509号提交中,2019年9月30日)derrickstolee
gitster

fetch:添加fetch.writeCommitGraph配置设置

提交图功能现在默认情况下处于启用状态,默认情况下在' git gc' 期间写入。
通常,只有当' git gc --auto'命令将gc.auto设置传递给实际工作时,Git才会写入提交图。这意味着提交图通常会落后于每天使用的提交。

要保持最新的提交,请在' git fetch'上添加一个步骤以在获取新对象后编写提交图。
fetch.writeCommitGraph配置设置允许写拆分提交-图形,因此平均写入该文件的成本是非常小的。有时,提交图链会崩溃到单个级别,这对于非常大的回购来说可能很慢。

如果feature.experimental要启用其他功能,请将默认值调整为true 。


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

请参阅Jeff King()的commit 6abada1commit fbab552(2019年9月12日(通过合并JUNIOÇ滨野- -提交098e8c6,2019年10月7日)peff
gitster

commit-graphDIE_ON_LOAD检查实际加载时间

Commit 43d3561(commit-graph编写:如果现有图形损坏,不要死,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
 

  • 如果您使用的是 git 版本 2.29 或更高版本,您应该运行 `git commit-graph write --reachable --changed-paths`。这将预先计算文件路径,以便作用域为文件的“git log”命令也受益于此缓存。 (2认同)

Sch*_*ern 5

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

--numstat是什么让一切变慢,否则git-log即使格式化也可以在 1 秒内完成。进行 diff 的成本很高,因此如果您可以将其从流程中删除,将会极大地加快速度。也许事后才这样做。

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


msw*_*msw 3

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

\n\n

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

\n\n
git log --pretty=format:%H\\t%ae\\t%an\\t%at\\t%s --numstat > log-pretty.txt\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
$ tail -1 log-pretty.txt\n30  0   railties/test/webrick_dispatcher_test.rb\n$ time grep railties/test/webrick_dispatcher_test.rb log-pretty.txt \n\xe2\x80\xa6\n30  0   railties/test/webrick_dispatcher_test.rb\n\nreal    0m0.012s\n\xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n