我们正在为我们的项目使用 Git。存储库相当大(.git
文件夹大约 8Gb)。
我们正在使用git checkout -f
post-receive 挂钩来更新工作树。
问题是,即使是几个稍有改动的文件也需要很长时间,大约 20 秒。我不知道为什么这么长。
可能是存储库大小的问题吗?
我应该尝试哪些步骤或工具来进一步定位和调查问题?
感谢您的任何帮助。
问候,亚历克斯
我们正在为我们的项目使用 Git。存储库相当大(.git
文件夹大约 8Gb)。
我们正在使用git checkout -f
post-receive 挂钩来更新工作树。
问题是,即使是几个稍有改动的文件也需要很长时间,大约 20 秒。我不知道为什么这么长。
可能是存储库大小的问题吗?
我应该尝试哪些步骤或工具来进一步定位和调查问题?
感谢您的任何帮助。
问候,亚历克斯
原始答案(2012 年 11 月)
.git
我确认如果你保持一个大的 git 目录( ),git 会大大减慢。
你可以在这个线程中看到一个插图(不是因为大文件,而是因为大量文件和提交历史):
测试存储库有 400 万次提交、线性历史记录和大约 130 万个文件。
目录大小.git
约15GB,已重新打包为'
git repack -a -d -f --max-pack-size=10g --depth=100 --window=250
这个重新包装在一台强大的机器上花了大约 2 天时间(即,大量的 ram 和闪存)。
索引文件的大小为 191 MB。
至少,您可以考虑拆分存储库,将二进制文件隔离在自己的 git 存储库中,并使用子模块在源存储库和二进制存储库之间进行跟踪。
最好在源引用之外存储大型二进制文件(特别是如果它们是生成的)。
建议使用“工件”存储库,例如Nexus。
似乎保留这些二进制文件的所有 git 解决方案是 git-annex 或 git-media,如“如何处理大型 git 存储库? ”中所述。
2016 年 2 月更新:git 2.8(2016 年 3 月)应该会显着提高git checkout
性能。
请参阅提交 a672095(2016 年 1 月 22 日)和提交 d9c2bd5(2015 年 12 月 21 日)由David Turner ( dturner-tw
)。
(由Junio C Hamano 合并 -- gitster
--在提交 201155c中,2016 年 2 月 3 日)
unpack-trees
:修复意外的二次行为
在解包树时(例如在 期间
git checkout
),当我们遇到一个超出我们路径的缓存条目时,我们会切断迭代。
master^20000
这为 master 和Twitter 的 monorepo之间的 git checkout 提供了大约 45% 的加速 。
一般来说,加速取决于存储库结构、更改数量和打包文件的决定。
do_compare_entry
: 使用已经计算好的路径在 traverse_trees 中,我们为 a 生成完整的遍历路径
traverse_info
。
后来,在 do_compare_entry 中,我们过去常常做一堆工作来比较traverse_info
cache_entry 的名称,而不计算该路径。
但是由于我们已经有了这条路,我们不需要做所有的工作。
相反,我们可以将生成的路径放入 中traverse_info
,并更直接地进行比较。这使得
git checkout
速度更快——在 Twitter 的 monorepo 上大约 25%。
较深的目录树可能比较浅的目录树受益更多。
使用sparse-checkout,可以大大加快对大型存储库的签出速度。
而在 Git 2.33(2021 年第三季度)中,这种情况得到了更大的改善,其中“ git checkout
” (man)和(man)学会了在没有不必要地扩展稀疏索引的情况下工作。git commit
请参阅Derrick Stolee ( ) 的提交 e05cdb1、提交 70569fa(2021 年 7 月 20 日)和提交 1ba5f45、提交 f934f1b、提交 daa1ace、提交 11042ab、提交 0d53d19(2021 年 6 月 29 日)。(由Junio C Hamano 合并 -- --在提交 506d2a3中,2021 年 8 月 4 日)derrickstolee
gitster
checkout
: 停止扩展稀疏索引签字人:Derrick Stolee
以前的更改对稀疏索引进行了必要的改进
unpack-trees.c
,diff-lib.c
以便根据其与树的比较来修改稀疏索引。
剩下的唯一工作是删除一些ensure_full_index()
调用并添加测试,以验证在我们有趣的案例中索引没有扩展。
在这些测试中包含“switch”和“restore”,因为它们与“checkout”共享一个基本实现。以下是 p2000-sparse-operations.sh 的相关性能结果:
Test HEAD~1 HEAD -------------------------------------------------------------------------------- 2000.18: git checkout -f - (full-v3) 0.49(0.43+0.03) 0.47(0.39+0.05) -4.1% 2000.19: git checkout -f - (full-v4) 0.45(0.37+0.06) 0.42(0.37+0.05) -6.7% 2000.20: git checkout -f - (sparse-v3) 0.76(0.71+0.07) 0.04(0.03+0.04) -94.7% 2000.21: git checkout -f - (sparse-v4) 0.75(0.72+0.04) 0.05(0.06+0.04) -93.3%
将完整索引情况与稀疏索引情况进行比较很重要,因为先前的稀疏索引结果被索引扩展夸大了。
对于索引 v4,这是 88% 的改进。在 HEAD 具有超过 200 万条路径和包含约 60,000 条路径的稀疏签出定义的内部存储库中,“
git checkout
” (man)在此更改后从 3.5 秒变为 297 毫秒。
仅存在这约 60,000 个路径的理论最佳值是 275 毫秒,因此额外的稀疏目录条目会产生 22 毫秒的开销。
解决此问题的另一种方法是并行结帐(从 Git 2.32 开始,2021 年第二季度)。
如本补丁中所述(仍在进行中):
该系列为结账机器添加了并行工作人员。
缓存条目分布在负责读取、过滤和将 blob 写入工作树的助手进程中。
这应该有利于所有调用unpack_trees()
or的命令check_updates()
,例如:checkout、clone、sparse-checkoutcheckout-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 更改。
请参阅Matheus Tavares ( )的提交 ae22751、提交 30419e7、提交 584a0d1、提交 49cfd90、提交 d052cc0(2021 年 3 月 23 日) 。
请参阅Jeff Hostetler ( )的提交 f59d15b、提交 3e9e82c、提交 55b4ad0、提交 38e9584(2020 年 12 月 16 日) 。(由Junio C Hamano 合并 -- --在提交 c47679d中,2021 年 4 月 2 日)matheustavares
Jeff-Hostetler
gitster
convert
: 添加[async_]convert_to_working_tree_ca()
变体签字人:Jeff Hostetler
签字人:Matheus Tavares
_ca()
通过添加转换函数的变体,将属性收集与实际转换分开。
这些变体接收预先计算的“结构conv_attrs
”,因此不依赖于索引状态。
它们将在未来的补丁中使用,添加并行结账支持,原因有两个:
- 我们将在转换
checkout_entry()
之前将转换属性加载到 中,以确定路径是否符合并行结帐条件。
因此,稍后再次加载它们对于实际转换将是浪费的。- 并行工作人员将负责读取、转换并将 blob 写入工作树。
他们将无法访问主进程的索引状态,因此他们无法加载属性。
相反,它们将接收预加载的并调用_ca()
转换函数的变体。
此外,属性机制经过优化,可以按顺序处理路径,因此最好将其留给主进程。
和:
在 Git 2.32(2021 年第 2 季度)中,结账机器被教导在可能的情况下并行执行文件的实际写出。
请参阅Matheus Tavares ( ) 的提交68e66f2 ( 2021 年 4 月 19 日)和提交 1c4d6f4、提交 7531e4b、提交 e9e8adf、提交 04155bd(2021 年 4 月 18 日)。(由Junio C Hamano 合并 -- --在提交 a1cac26中,2021 年 4 月 30 日)matheustavares
gitster
parallel-checkout
: 添加配置选项合着:Jeff Hostetler 署名
:Matheus Tavares
通过引入两个新设置使并行结帐可配置:>-
checkout.workers
和
checkout.thresholdForParallelism
.
第一个定义工作人员的数量(其中一个表示顺序结帐),第二个定义尝试并行结帐的最小条目数。为了确定 checkout.workers 的默认值,并行版本在 linux repo 中的三个操作中使用冷缓存进行了基准测试:克隆 v5.8、从 v2.6.15 签出 v5.8(签出 I)和签出 v5。 8 从 v5.7(结帐 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”设置中执行了另一个基准测试,其中并行校验显示是有益的。
这一次,我们在从 Linux 工作树中随机删除越来越多的文件后,比较了( man )的运行时间,有无并行性。 下面的“顺序回退”列对应于 checkout.workers 为 10 但等于要更新的文件数加一的执行(因此我们最终按顺序写入)。 每个测试用例采样 15 次,每个样本随机删除一组不同的文件。 结果如下:git checkout -f
checkout.thresholdForParallelism
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 个文件,执行时间会随着文件数量的增加而增加,正如人们所期望的那样。关于测试环境:本地 SSD 测试在运行 Manjaro Linux 的 i7-7700HQ(4 核超线程)上执行。
本地硬盘测试在运行 Debian 的 Intel(R) Xeon(R) E3-1230(也是 4 核超线程)、硬盘 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
现在在其手册页中包含:
checkout.workers
更新工作树时要使用的并行工作程序的数量。默认值为1,即顺序执行。如果设置为小于 1 的值,Git 将使用与可用逻辑核心数量一样多的工作程序。此设置
checkout.thresholdForParallelism
会影响执行检出的所有命令。例如结帐、克隆、重置、稀疏结帐等。注意:并行签出通常为位于 SSD 或 NFS 上的存储库提供更好的性能。对于旋转磁盘和/或内核数量较少的机器上的存储库,默认的顺序签出通常执行得更好。存储库的大小和压缩级别也可能影响并行版本的执行情况。
checkout.thresholdForParallelism
当使用少量文件运行并行检出时,子进程生成和进程间通信的成本可能超过并行化收益。
此设置允许定义应尝试并行检出的最小文件数。
默认值为 100。
而且,仍然使用 Git 2.32(2021 年第二季度),“并行结账”的最后一部分:
请参阅Matheus Tavares ( )的提交87094fc、提交 d590422、提交 2fa3cba、提交 6a7bc9d、提交 d0e5d35、提交 70b052b、提交 6053950、提交 9616882(2021 年 5 月 4 日) 。(由Junio C Hamano 合并 -- --在提交 a737e1f中,2021 年 5 月 16 日)matheustavares
gitster
checkout-index
:添加并行结帐支持签字人: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()
不会返回错误,它exit()
使用代码 128 调用。我们可以在退出之前调用它,但是如果我们run_parallel_checkout()
将两种 checkout-index 模式的退出路径统一为cmd_checkout_index()
注意与并行结帐 API 的交互。
所以让我们这样做。有了这个变化,我们还必须考虑是否要继续使用 128 作为( man )的错误代码,而我们使用 1 作为( man )(即使实际错误相同)。 由于仅将代码 128 用于 没有太大价值,并且文档中没有提及它(因此更改它不太可能破坏任何现有脚本),让我们让两种模式在错误时以代码 1 退出。
git checkout-index --all
git checkout-index
<path>
--all
checkout_entry()
在 Git 2.33(2021 年第三季度)之前,并行结帐代码路径没有初始化用于以面向未来的方式与工作进程对话的对象 ID 字段。
请参阅Matheus Tavares ( ) 的提交 3d20ed2(2021 年 5 月 17 日)。(由Junio C Hamano 合并 -- --在bb6a63a 提交中,2021 年 6 月 10 日)matheustavares
gitster
parallel-checkout
:将新的object_id
算法字段发送给工人签字人:Matheus Tavares
存储 SHA-1 名称的
object_id
哈希数组末尾有一些未使用的字节。
由于不使用这些字节,它们通常也不初始化为任何值。
但是,parallel_checkout.c:send_one_item()
缓存object_id
条目会被复制到缓冲区中,该缓冲区稍后会通过管道发送给结账工作人员write()
。
这使得 Valgrind 抱怨将未初始化的字节传递给系统调用。但是,由于cf09832 (
hash
: add an algo member to struct object_id, 2021-04-26, Git v2.32.0-rc0 -- merge在第 #15 批中列出) ("hash: add an algo member to structobject_id",
2021-04-26 ),hashcpy()
在这里使用不再足够,因为它不会从 复制新的算法字段object_id
。
让我们添加并使用一个新函数,它满足我们复制所有重要object_id
数据的要求,同时仍然避免未初始化的字节,通过填充结尾目标中的哈希数组object_id
。
有了这个改变,我们也不再需要send_one_item()
用零初始化目标缓冲区,所以让我们从xcalloc()
to切换到xmalloc()
清楚这一点。