接收后挂钩的巨大Git存储库检查速度非常慢

时间:2012-11-06 13:02:29

标签: git git-checkout

我们正在为我们的项目使用Git。存储库相当庞大(.git文件夹约为8Gb)。

我们在post-receive hook中使用“git checkout -f”来更新工作树。

问题是即使是几个略微改变的文件,检查也需要很长时间,大约20秒。我不知道为什么会这么久。

可能是存储库大小的问题吗?

我应该尝试使用哪些步骤或工具来进一步查找和调查问题?

感谢您的帮助。

此致 亚历

2 个答案:

答案 0 :(得分:1)

原始答案(2012年11月)

如果你保持一个大的git目录(.git),我确认git会大大减慢。

您可以在this thread中看到插图(不是因为大文件,而是因为有大量文件和提交历史记录):

  

测试回购包含400万次提交,线性历史记录和约130万个文件   .git目录的大小约为15GB,并已使用'

重新打包
git repack -a -d -f --max-pack-size=10g --depth=100 --window=250
  

这个重新包装在一台强壮的机器上花了大约2天(即,大量的ram和闪光灯)   索引文件的大​​小为191 MB。

至少,您可以考虑拆分repo,在自己的git仓库中隔离二进制文件,并使用 submodules 来跟踪源代码库和二进制存储库。

最好在 source 参考之外存储大型二进制文件(特别是如果它们生成)。
建议使用“工件”存储库,例如Nexus

的所有git解决方案保留这些二进制文件是git-annex或git-media,如“How to handle a large git repository?”中所示。


2016年2月更新:git 2。8(2016年3月)应该会显着提高git checkout的表现。

commit a672095(2016年1月22日)和commit d9c2bd5(2015年12月21日)David Turner (dturner-tw)Junio C Hamano -- gitster --于2016年2月3日commit 201155c合并)

  

unpack-trees:修复意外的二次行为

     

在解压缩树时(例如在git checkout期间),当我们点击超出路径的缓存条目时,我们会切断迭代。

     

这为主人和主人之间的git结账提供了大约45%的加速   推特的monorepo上的master^20000   加速一般取决于转发结构,变更数量和包装文件包装决策。

     

do_compare_entry:使用已计算的路径

     

在traverse_trees中,我们为traverse_info生成完整的遍历路径   之后,在do_compare_entry中,我们过去常常做一些工作来将traverse_info与cache_entry的名称进行比较而不计算该路径。
  但既然我们已经走了这条道路,那么我们就不需要做那么多工作了   相反,我们可以将生成的路径放入traverse_info,并更直接地进行比较。

     

这使git checkout更快 - 在Twitter上大约25%   monorepo。较深的目录树可能比较浅的目录树受益

答案 1 :(得分:0)

解决此问题的另一种方法是并行结帐(从 Git 2.32 开始,2021 年第二季度)。
this patch (still in progress)中所述:

<块引用>

这个系列为结账机器添加了并行工作人员。

缓存条目分布在辅助进程之间,它们负责 读取、过滤和写入 blob 到工作树。
这应该有利于所有调用 unpack_trees()check_updates() 的命令, 例如:结帐、克隆、稀疏结帐、checkout-index

Local:

           Clone                  Checkout I             Checkout II
Sequential  8.180 s ± 0.021 s      6.936 s ± 0.030 s      2.585 s ± 0.005 s
10 workers  3.406 s ± 0.187 s      2.164 s ± 0.033 s      1.050 s ± 0.021 s
Speedup     2.40 ± 0.13            3.21 ± 0.05            2.46 ± 0.05

例如,在 Git 2.32(2021 年第 2 季度)中,对并行结帐进行了准备性 API 更改。

commit ae22751commit 30419e7commit 584a0d1commit 49cfd90commit d052cc0Matheus Tavares (matheustavares)(2021 年 3 月 23 日)。
请参阅 commit f59d15bcommit 3e9e82ccommit 55b4ad0commit 38e9584Jeff Hostetler (Jeff-Hostetler)(2020 年 12 月 16 日)。
(由 Junio C Hamano -- gitster --commit c47679d 合并,2021 年 4 月 2 日)

<块引用>

convert:添加 [async_]convert_to_working_tree_ca() 变体

签字人:Jeff Hostetler
签字人:Matheus Tavares

<块引用>

通过添加转换函数的 _ca() 变体,将属性收集与实际转换分开。
这些变体接收预先计算的“struct conv_attrs”,因此不依赖于索引状态。
它们将在未来添加并行结账支持的补丁中使用,原因有两个:

  • 在转换之前,我们将在 checkout_entry() 中加载转换属性,以确定路径是否符合并行结帐条件。
    因此,稍后为实际转换再次加载它们会很浪费。
  • 并行工作者将负责读取、转换和写入 Blob 到工作树。
    他们将无法访问主进程的索引状态,因此他们无法加载属性。
    相反,它们将接收预加载的函数并调用转换函数的 _ca() 变体。
    此外,属性机制经过优化以按顺序处理路径,因此无论如何最好将其留给主进程。

还有:

在 Git 2.32(2021 年第 2 季度)中,结账机制被教导在可能的情况下并行执行文件的实际写出。

参见 commit 68e66f2(2021 年 4 月 19 日)和 commit 1c4d6f4commit 7531e4bcommit e9e8adfcommit 04155bd(2021 年 4 月 18 日)由 Matheus Tavares (matheustavares) .
(2021 年 4 月 30 日在 Junio C Hamano -- gitster --commit a1cac26 合并)

<块引用>

parallel-checkout:添加配置选项

合著者:Jeff Hostetler
签字人:Matheus Tavares

<块引用>

通过引入两个新设置使并行结账可配置:>- checkout.workers

  • checkout.thresholdForParallelism
    第一个定义工作人员的数量(其中一个表示顺序结帐),第二个定义尝试并行结帐的最小条目数。

为了决定 checkout.workers 的默认值,并行版本在 linux repo 中的三个操作中进行了基准测试,使用冷缓存:克隆 v5.8,从 v2.6.15 检出 v5.8(结帐 I)和检查从 v5.7 中取出 v5.8(结帐 II)。
下面的四个表显示了 5 次运行的平均运行时间和标准偏差:SSD 上的本地文件系统、HDD 上的本地文件系统、Linux NFS 服务器和 Amazon EFS(均在 Linux 上)。
每个并行结帐测试都使用在该环境中带来最佳整体结果的工作人员数量执行。

本地 SSD:

             Sequential             10 workers            Speedup
Clone        8.805 s ± 0.043 s      3.564 s ± 0.041 s     2.47 ± 0.03 
Checkout I   9.678 s ± 0.057 s      4.486 s ± 0.050 s     2.16 ± 0.03 
Checkout II  5.034 s ± 0.072 s      3.021 s ± 0.038 s     1.67 ± 0.03  

本地硬盘:

             Sequential             10 workers             Speedup
Clone        32.288 s ± 0.580 s     30.724 s ± 0.522 s    1.05 ± 0.03 
Checkout I   54.172 s ±  7.119 s    54.429 s ± 6.738 s    1.00 ± 0.18 
Checkout II  40.465 s ± 2.402 s     38.682 s ± 1.365 s    1.05 ± 0.07  

Linux NFS 服务器(v4.1,在 EBS 上,单可用区):

             Sequential             32 workers            Speedup
Clone        240.368 s ± 6.347 s    57.349 s ± 0.870 s    4.19 ± 0.13 
Checkout I   242.862 s ± 2.215 s    58.700 s ± 0.904 s    4.14 ± 0.07 
Checkout II  65.751 s ± 1.577 s     23.820 s ± 0.407 s    2.76 ± 0.08  

EFS(v4.1,跨多个可用区复制):

             Sequential             32 workers            Speedup
Clone        922.321 s ± 2.274 s    210.453 s ± 3.412 s   4.38 ± 0.07 
Checkout I   1011.300 s ± 7.346 s   297.828 s ± 0.964 s   3.40 ± 0.03 
Checkout II  294.104 s ± 1.836 s    126.017 s ± 1.190 s   2.33 ± 0.03  

上述基准测试表明,并行结帐对位于 SSD 或分布式文件系统上的存储库最有效。
对于旋转磁盘和/或旧机器上的本地文件系统,并行性并不总是带来良好的性能。
出于这个原因, checkout.workers 的默认值是一,又名
顺序结帐。

为了确定 checkout.thresholdForParallelism 的默认值,在“本地 SSD”设置中执行了另一个基准测试,其中并行结帐显示是有益的。
这一次,我们比较了 git checkout -f(man) 的运行时,在从 Linux 工作树中随机删除越来越多的文件后,有并行性和没有并行性。
下面的“顺序回退”列对应于 checkout.workers 为 10 但 checkout.thresholdForParallelism 等于要更新的​​文件数加一(因此我们最终按顺序写入)的执行。
每个测试用例采样 15 次,每个样本随机删除了一组不同的文件。
结果如下:

             sequential fallback   10 workers           speedup

10   files    772.3 ms ± 12.6 ms   769.0 ms ± 13.6 ms   1.00 ± 0.02 
20   files    780.5 ms ± 15.8 ms   775.2 ms ±  9.2 ms   1.01 ± 0.02 
50   files    806.2 ms ± 13.8 ms   767.4 ms ±  8.5 ms   1.05 ± 0.02 
100  files    833.7 ms ± 21.4 ms   750.5 ms ± 16.8 ms   1.11 ± 0.04 
200  files    897.6 ms ± 30.9 ms   730.5 ms ± 14.7 ms   1.23 ± 0.05 
500  files   1035.4 ms ± 48.0 ms   677.1 ms ± 22.3 ms   1.53 ± 0.09 
1000 files   1244.6 ms ± 35.6 ms   654.0 ms ± 38.3 ms   1.90 ± 0.12 
2000 files   1488.8 ms ± 53.4 ms   658.8 ms ± 23.8 ms   2.26 ± 0.12  

从以上数字来看,100 个文件似乎是阈值设置的合理默认值。

注意:多达 1000 个文件,我们观察到并行代码的执行时间随着文件数量的增加而下降。
这是一个相当奇怪的行为,但它在多次重复中被观察到。
超过 1000 个文件,执行时间会随着文件数量的增加而增加,正如人们所期望的那样。

关于测试环境:在运行 Manjaro Linux 的 i7-7700HQ(4 核超线程)上执行本地 SSD 测试。
本地硬盘测试在运行 Debian 的 Intel(R) Xeon(R) E3-1230(也是 4 核超线程)、HDD Seagate Barracuda 7200.14 SATA 3.1 上执行。
NFS 和 EFS 测试在具有 4 个 vCPU 的 Amazon EC2 c5n.xlarge 实例上执行。
Linux NFS 服务器在具有 2 个 vCPUS 和 1 TB EBS GP2 卷的 m6g.large 实例上运行。
在每次计时之前,linux 存储库被删除(或检出回到其先前状态),并执行 sync && sysctl vm.drop_caches=3

git config 现在包含在其 man page 中:

<块引用>

checkout.workers

更新工作树时要使用的并行工作线程数。 默认为 1,即顺序执行。如果设置为小于 超过 1,Git 将使用与逻辑内核数量一样多的工作线程 可用的。此设置和 checkout.thresholdForParallelism 会影响 执行结帐的所有命令。例如。结帐、克隆、重置、 稀疏结帐等

注意:并行结帐通常为存储库提供更好的性能 位于 SSD 或 NFS 上。用于旋转磁盘和/或机器上的存储库 对于少量内核,默认的顺序结账通常会执行 更好的。存储库的大小和压缩级别也可能影响 并行版本表现良好。

checkout.thresholdForParallelism

使用少量文件运行并行结帐时,成本 子进程生成和进程间通信的重要性可能超过 并行化收益。

此设置允许定义最小值 应尝试并行检出的文件数。

默认值为 100。


而且,仍然使用 Git 2.32(2021 年第二季度),“并行结账”的最后一部分:

commit 87094fccommit d590422commit 2fa3cbacommit 6a7bc9dcommit d0e5d35commit 70b052bcommit 6053950、{{3} }(2021 年 5 月 4 日)作者:commit 9616882
(2021 年 5 月 16 日在 Matheus Tavares (matheustavares)Junio C Hamano -- gitster -- 合并)

<块引用>

commit a737e1f:添加并行结账支持

签字人:Matheus Tavares

<块引用>

允许 checkout-index 使用并行结账框架,遵守 checkout.workers 配置。

checkout-index 中有两个代码路径调用 checkout_entry(),因此可以使用并行结帐:

  • checkout_file(),用于写入命令行中明确给出的路径;和
  • checkout_all(),当给定 --all 选项时,用于写入索引中的所有路径。

在两种操作模式下,checkout-index 不会在 checkout_entry() 失败时立即中止。
相反,它会尝试在以非零退出代码退出之前检查所有剩余路径。
为了在使用并行结账时保持这种行为,我们必须允许 run_parallel_checkout() 在退出之前尝试写入排队的条目,即使我们已经从之前的 checkout_entry() 调用中获得了错误代码。

然而,checkout_all() 不会在出现错误时返回,它使用代码 128 调用 exit()。我们可以让它在退出前调用 run_parallel_checkout(),但如果我们在 cmd_checkout_index() 处统一了两种结账索引模式的退出路径,并让该函数处理与并行结账 API 的交互。
所以让我们这样做。

有了这个变化,我们还要考虑是否要继续使用 128 作为 checkout-index(git checkout-index --all) 的错误代码,而我们使用 1 作为 { {3}}(man) <path>(即使实际错误相同)。
由于代码 128 仅用于 --all 没有多大价值,并且文档中没有提及它(因此更改它不太可能破坏任何现有脚本),让我们用代码 1 退出两种模式checkout_entry() 错误。


在 Git 2.33(2021 年第 3 季度)之前,并行结帐代码路径未初始化用于以面向未来的方式与工作进程通信的对象 ID 字段。

参见git checkout-indexman(2021 年 5 月 17 日)。
(2021 年 6 月 10 日于 commit 3d20ed2Matheus Tavares (matheustavares) 合并)

<块引用>

Junio C Hamano -- gitster --:将新的 object_id 算法字段发送给工作人员

签字人:Matheus Tavares

<块引用>

存储 SHA-1 名称的 object_id 在散列数组的末尾有一些未使用的字节。
由于这些字节没有被使用,它们通常也不会被初始化为任何值。
但是,在 parallel_checkout.c:send_one_item() 处,缓存条目的 object_id 被复制到缓冲区中,该缓冲区随后通过管道 write() 发送给结帐工作人员。
这让 Valgrind 抱怨将未初始化的字节传递给系统调用。

然而,由于 commit bb6a63a (hash: add a algo member to struct object_id, 2021-04-26, Git v2.32.0-rc0 -- parallel-checkout 列在{{3 }}) ("hash: add a algo member to struct object_id", 2021-04-26),在这里使用 hashcpy() 不再足够,因为它不会从 {{ 1}}.
让我们添加并使用一个新函数,它通过在目标 object_id 中填充散列数组的末尾来满足我们复制所有重要 object_id 数据的要求,同时仍然避免未初始化的字节。
通过此更改,我们也不再需要将 object_id 中的目标缓冲区初始化为零,因此让我们从 send_one_item() 切换到 xcalloc() 以明确这一点。