如何提高git日志性能?

时间:2016-02-03 20:15:47

标签: git

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

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

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

有没有办法改善这种表现?

4 个答案:

答案 0 :(得分: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在大型存储库上的性能。

答案 1 :(得分:3)

你是对的,在生成224&000; 000行(15MiB)输出的56和000次提交中生成报告确实需要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倍。

答案 2 :(得分:3)

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.18(2018年第二季度)将提高git log表现:

commit 902f5a2René Scharfe (rscharfe)(2018年3月24日) 请commit 0aaf05b查看commit 3d475f4Derrick Stolee (derrickstolee)(2018年3月22日) 请commit 626fd98brian m. carlson (bk2204)(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 repo的完全重新打包的副本,以下'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 7547b95commit 3d5df01commit 049d51acommit 177722bcommit 4f2542bcommit 1b70dfdcommit 2a2e32b(2018年4月10日) ,commit f237c8bcommit 08fd81ccommit 4ce58eecommit ae30d7bcommit b84f767commit cfe8321commit f2af9f5(2018年4月2日){ {3}}。
(由Derrick Stolee (derrickstolee)合并于Junio C Hamano -- gitster --,2018年5月8日)

  

commit:将提交图与提交解析

集成      

教Git检查提交图文件以提供a的内容   调用commit b10edb2时的struct commit   此实现满足struct commit的所有后置条件,包括加载父项,根树和提交日期。

     

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

     

在测试脚本parse_commit_gently()中,添加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%  |

要了解有关提交图的详情,请参阅“t5318-commit-graph.sh”。

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

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

How does 'git log --graph' work?commit 279ffad(2018年4月30日) SZEDER Gábor (szeder)commit 7b8a21dcommit 2e27bd7commit 5bb03decommit 891435d(2018年4月6日)。{
(由Derrick Stolee (derrickstolee)合并于Junio C Hamano -- gitster --,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 c89b6e1(201年1月7日)和commit 8be88dbcommit 4cea1cecommit d4e19e5(2019年1月6日)commit 0000d65(由René Scharfe (rscharfe)合并于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%

答案 3 :(得分:0)

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

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

请参见commit caf388c前的commit e369698(2020年4月9日)和Derrick Stolee (derrickstolee)(2020年3月30日)。
参见commit d5b873ccommit a759bfacommit 42e50e7commit a56b946commit d38e07bcommit 1217c03commit 76ffbca(2020年4月6日)和{ {3}}的{3}},commit 3d11275commit f97b932commit ed591fecommit f1294eacommit f52207a(2020年3月30日)。
请参见commit 3be7efcGarima Singh (singhgarima)(2020年3月30日)。
(由commit d21ee7dJeff King (peff)中合并,2020年5月1日)

Junio C Hamano -- gitster --:使用Bloom过滤器来加快基于路径的修订过程

帮助者:Derrick Stolee
帮助人:SZEDERGábor
帮助人:Jonathan Tan
签名人:Garima Singh

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

我们在prepare_revision_walk步骤中加载Bloom过滤器,目前仅在处理单个pathspec时进行。
将来可以在本系列文章的基础上探索并构建将其扩展为可用于多种路径规格的方法。

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

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

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

性能提升:我们在git仓库,Linux和一些内部大型仓库上测试了git log -- <path>的性能,并使用了各种深度不同的路径。

关于git和linux仓库:

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

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

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

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

请参见commit 9b6606frevision.c(2020年5月4日)。
(由commit fbda77cJonathan Tan (jhowtan)中合并,2020年5月8日)

Junio C Hamano -- gitster --:避免内存泄漏

签名人:Jonathan Tan
作者:Derrick Stolee

parse_commit_graph()创建结构bloom_filter_settings时由于错误而在commit 95875e0提供的入口点上运行的模糊器发现内存泄漏。

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

在进行更改的同时,我还注意到了另一个可能的内存泄漏-提供BLOOMDATA块而不提供BLOOMINDEXES时。
同时解决该错误。


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

请参见commit-graphfuzz-commit-graph.c(2020年5月11日)。
请参阅{{ 3}}。
(由commit b928e48SZEDER Gábor (szeder)中合并,2020年5月14日)

commit 2f6775f:删除重复的目录条目

签名人:Derrick Stolee

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

但是,当前代码在此过程中做得不好。

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

hashmap_get()之前正确使用hashmap_add()
另外,请确保包含比较功能,以便可以正确匹配它们。

这会影响t0095-bloom.sh中的测试。
这很有意义,“ smallDir”内部有十个更改,因此过滤器中的路径总数应为11。
这将需要11 * 10位,每字节8位,则需要14个字节。


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

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

请参见commit 65c1a28commit 8809328(2020年5月11日)。
请参见commit 891c17c之前的commit 54c337bcommit eb591e4Derrick Stolee (derrickstolee)Junio C Hamano -- gitster --(2020年5月11日)。
(由commit 4b1e5e5bloom中合并,2020年6月9日)

commit f32dde8:与changed-path布隆过滤器集成

签名人:Derrick Stolee

以前对线路记录机制的更改着眼于使第一个结果显示更快。这是通过在返回早期结果之前不再遍历整个提交历史来实现的。
还有另一种提高性能的方法:走路最多可以更快。让我们使用更改路径的布隆过滤器来减少计算差异所需的时间。

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

我们必须确保在Derrick Stolee (derrickstolee)中调用prepare_to_use_bloom_filters(),以便将bloom_filter_settings从提交图加载到结构rev_info中。
当然,在某些情况下仍然禁止使用,但是在line-log情况下,以不同于正常方式的方式提供pathspec。

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

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

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

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

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

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

为了进行测试,我使用以下命令(commit 002933f)选择了在前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年第四季度)中,采用了独立实现的思想来改进了路径更改布隆过滤器。

请参见commit 3cb9d2bcommit 48da94bcommit d554672SZEDER Gábor (szeder)Junio C Hamano -- gitster --commit c3a0282line-log,{{3} },revision.cstolen from StackOverflow(2020年6月5日)由commit 7fbfe07
(由commit bb4d60ecommit 5cfa438中合并,2020年7月30日)

commit 2ad4f1a:简化parse_commit_graph()#1

签名人:SZEDERGábor
签名人:Derrick Stolee

当我们遍历Chunk Lookup表的所有条目时,请确保我们不要试图读取超过mmap-ed commit-graph文件末尾的内容,并在每次迭代中检查块ID和偏移量即将读取的内容仍在映射内存区域内。但是,在每次迭代中进行这些检查并不是真正必要的,因为在此循环之前,从刚刚解析的提交图头中就已经知道了提交图文件中的块数。

因此,在开始遍历那些条目并删除每个迭代检查之前,让我们检查提交图文件是否足够大,以适合块查找表中的所有条目。
在此过程中,请考虑拥有有效提交图文件所需的所有内容的大小,即标头的大小,强制性OID Fanout块的大小以及预告片中签名的大小

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

还有commit fa79653

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

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