【问题标题】:Speed up `git blame` on repository with many commits通过许多提交加速存储库上的`git blame`
【发布时间】:2020-01-10 06:35:36
【问题描述】:

我正在尝试git blame以下文件(在我的本地机器上运行),因为它太慢而无法生成 GitHub 的责备:

https://github.com/Homebrew/homebrew-core/blob/master/Formula/sqlite.rb

但是在本地运行也很慢,在我的机器上运行一分钟以上

time git --no-pager blame Formula/sqlite.rb > /dev/null

存储库包含超过 150K 的提交。

有没有办法加快git blame 命令的速度?

【问题讨论】:

  • 我想在几秒钟内得到结果。但在我的机器上花了一分钟多的时间。我认为问题不是特定于该文件。
  • 这在我的机器上也需要一分钟。我怀疑是大量的提交使得这需要这么长时间。我没有答案,但我在你的问题中添加了一些细节。也许其他人现在可以提供帮助。

标签: git git-blame


【解决方案1】:

使用 Git 2.27(2020 年第二季度),“git blame”学会利用存储在提交图文件中的“changed-pathsBloom filterintroduced with git log

请参阅commit 1b4c57fcommit 24b7d1ecommit fe88f9f(2020 年 4 月 23 日)Jeff King (peff)
请参阅Derrick Stolee (derrickstolee)commit 0906ac2commit b23ea97commit 8918e37(2020 年 4 月 16 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit 6d56d4c,2020 年 5 月 1 日)

blame: 使用 changed-path 布隆过滤器

签字人:Derrick Stolee

changed-path Bloom 过滤器有助于减少历史查询期间所需的树解析量

在计算差异之前,我们可以询问过滤器是否在提交和它的第一个父项之间更改了路径。

  • 如果过滤器说“否”,那么我们可以继续进行,而无需解析树。
  • 如果过滤器显示“可能”,那么我们会解析树以发现答案实际上是“是”还是“否”。

在计算责备时,find_origin() 中有一个部分计算提交与其父项之间的差异。
当这是第一个父级时,我们可以在调用 diff_tree_oid() 之前检查 Bloom 过滤器。

为了使这项工作与责任机制一起工作,我们需要用初始路径初始化一个结构bloom_key。但是,如果检测到重命名,我们需要向列表中添加更多键。然后我们检查这些键中的任何是否在差异中回答“可能”。

如果用户使用“git blame -C”请求复制检测,则“重要”文件集可以扩展的位置更多。我不太了解这在责备机制中是如何发生的。
因此,布隆过滤器集成在此模式下被显式禁用。
稍后的更改可以通过对add_bloom_key() 的适当调用(或调用)来扩展bloom_key 数据。

一般来说,这是一种性能增强,不应以任何方式改变“git blame”的行为。
如果 repo 有一个带有计算出的更改路径 Bloom 过滤器的提交图文件,那么他们应该注意到他们的“git blame”命令的性能有所提高。

以下是我通过归咎于 Linux 内核存储库中的一些路径发现的一些示例时序:

我专门寻找也被多次编辑的“深层”路径。
作为对比,MAINTAINERS 文件被多次编辑,但位于根树中。
这意味着计算相对于路径规范的差异的成本非常小。以下是该命令的时间安排:

这些时间是五个中最好的。
两种情况下,最坏情况的运行时间约为 2.5 分钟。
请注意,MAINTAINERS 文件在 17,000 多次提交中有 18,740 行。这恰好是此更改提供的改进最少的情况之一。

MAINTAINERS 文件缺乏改进以及其他示例相对适度的改进可以很容易地解释。
责备机制需要计算行级差异以确定每次提交更改了哪些行。这占了计算时间的很大一部分,并且此更改不会尝试改进算法的这一部分。
MAINTAINERS 文件很大并且经常更改,因此需要时间来确定哪些行由哪个提交更新。相比之下,代码文件要小得多,计算 Linux 邮件列表中单个补丁的逐行差异需要更长的时间。

在“-C”集成之外,我相信在此补丁之后,“git blame”的更改路径布隆过滤器几乎没有什么好处。


不过,请确保使用 Git 2.29(2020 年第四季度),因为存在一个小错误:

参见Edmundo Carmona Antoranz (eantoranz)commit 1302bad(2020 年 9 月 8 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit e1dd499,2020 年 9 月 18 日)

blame.c:将!oidcmp 的实例替换为oideq

签字人:Edmundo Carmona Antoranz

0906ac2b ("blame: use changed-path Bloom filters", 2020-04-16, Git v2.27.0-rc0 -- mergebatch #6 中列出) 引入了对 oidcmp() 的调用应该是oideq(),在14438c44中引入(“引入hasheq()oideq()”,2018-08-28,Git v2.20.0-rc0——mergebatch #1中列出) .


在 Git 2.29(2020 年第四季度)中,“git commit-graph(man) write”学会了使用 --max-new-filters 选项限制从头开始计算的布隆过滤器的数量。

这将使git blame受益。

请参阅commit d356d5dcommit 98bb796commit 59f0d50commit 97ffa4f(2020 年 9 月 17 日)、commit 809e032(2020 年 9 月 18 日)、commit 9a7a9edcommit 312cff5(2020 年 9 月 16 日)和@987654 @、commit 24f951acommit ab14d06commit 025d529commit 4f36440(2020 年 9 月 9 日)Taylor Blau (ttaylorr)
请参阅 Derrick Stolee (derrickstolee)commit b16a827(2020 年 9 月 16 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 288ed98,2020 年 9 月 29 日)

builtin/commit-graph.c: 引入'--max-new-filters='

帮助:Junio C Hamano
签字人:Taylor Blau

引入一个命令行标志来指定 'git commit-graph write'(man) 愿意从头开始计算的新 Bloom 过滤器的最大数量。

在此补丁之前,使用“--changed-paths”写入的提交图将为尚未计算的所有选定提交计算 Bloom 过滤器(即,之前使用“--split”写入的提交图以便执行汇总或替换)。

由于多种原因,此行为可能会导致提交图写入过长:

  • 可能有很多过滤器需要很长时间才能生成差异(例如,它们的更改数量接近最大值,差异本身需要很长时间等)。
  • 旧式提交图(它对具有太多条目的过滤器进行编码,因为根本没有计算过)导致我们浪费时间重新计算似乎没有计算过的过滤器,只是发现它们太大了。

这会使 'git commit-graph write --changed-paths'(man) 所需时间的上限变得相当不可预测。

为了使该命令的行为更具可预测性,引入“--max-new-filters=<n>”以允许从头开始计算最多“<n>”布隆过滤器。
这可以让“计算”已知过滤器快速进行,同时限制 Git 愿意执行的慢速任务的数量。

git commit-graph 现在包含在其man page 中:

使用--max-new-filters=<n> 选项,最多生成n new Bloom 过滤器(如果指定了 --changed-paths)。
如果n-1,则不强制执行限制。
只有新层中存在的提交才计入此限制。
要在较早的层上追溯计算布隆过滤器,建议使用--split=replace


使用 Git 2.31(2021 年第一季度),优化“git blame(man)

参见 Rafael Silva (raffs)commit 8e16eff(2021 年 2 月 17 日)。
(由 Junio C Hamano -- gitster -- 合并到 commit 18decfd,2021 年 2 月 25 日)

blame:删除不必要的get_commit_info()

签字人:Rafael Silva
审核人:Taylor Blau

git blame(man)--color-by-age 时,将调用determine_line_heat() 以根据提交的作者日期选择如何为输出着色。
它使用get_commit_info() 将信息解析为commit_info 结构,然而,这实际上是不必要的,因为determine_line_heat() 调用者也这样做。

相反,让我们将determine_line_heat() 更改为采用commit_info 结构并删除对get_commit_info() 的内部调用,从而清理和优化代码路径。

启用 Git 的 trace2 API 以记录每次调用 determine_line_heat() 函数的执行时间:

+ trace2_region_enter("blame", "determine_line_heat", the_repository);
  determine_line_heat(ent, &default_color);
+ trace2_region_enter("blame", "determine_line_heat", the_repository);

然后,在 linux.git 中为“kernel/fork.c”运行 git blame 并将每次调用的所有执行时间(大约 1.3k 调用)相加,可以将执行速度提高 2.6 倍(最好是 3):

git built from 328c109303 (The eighth batch, 2021-02-12) = 42ms
git built from 328c109303 + this change                  = 16ms

【讨论】:

  • 此外,您可以尝试运行例如git repack -f -a -d --depth=5 --window=15 如果您愿意为存储库花费额外的磁盘空间以减少 CPU 负载。它重新打包您的整个存储库以使用更小的“深度”,这会增加磁盘使用率,但会减少所有未来操作的 CPU 使用率。这需要运行一次,然后您可以将结果用于您想要运行的所有 git 命令(包括blame)。请注意,重新打包的结果是永久的,git 以后不会自动重新打包。如果减少 window,重新打包会更快,但磁盘使用量会增加。
  • @MikkoRantalainen 感谢您的反馈。我将在我自己的存储库上进行测试。
【解决方案2】:

按照 Git 标准,homebrew-core 存储库相当大。一个 250 MB 的存储库,4000 个“公式”的 150,000 次提交。这会影响性能。 Github 确实有问题。

git blame Formula/sqlite.rb 在我的 2018 i7 Macbook 上使用 Git 2.22.0 大约需要 45 秒。按照 Git 标准来说速度很慢,但考虑到运行git blame 的频率,这是可以接受的。

作为此存储库的用户,没有太多工作要做。 git blame 必须向后搜索每个提交以查看哪些提交更改了此文件。不幸的是,git blame 似乎没有利用并行处理。

有一些选择...

  1. 请与 Github 联系以解决此问题,希望他们能够解决。
  2. 限制你在历史中回溯多远:git blame --since=1.year -- Formula/sqlite.rb
  3. 重新考虑在此 repo 上需要快速git blame 的任何流程。
  4. 缓存结果。

【讨论】:

    猜你喜欢
    • 2011-05-09
    • 2011-10-30
    • 2019-09-16
    • 2015-04-20
    • 2010-10-23
    • 2011-09-03
    • 2011-06-06
    • 2012-10-17
    相关资源
    最近更新 更多