请注意,Git 2.25 修复了在拥有超过 1023 个包时 pack-objects 的极度减速问题。下面是具体数字。
另一个选项是 Git 2.38 (2022 年第三季度) 提出了新设置
git -c push.useBitmaps=false push
,以禁用
git push
的打包。
但是对于 Git 2.25 的修复:
这可能会对你的情况产生积极影响,特别是当你拥有大量的包文件时。
查看
提交 f66e040(2019年11月11日)由
Jeff King (peff
) 提交。
(合并于 提交 8faff38,由 Junio C Hamano -- gitster
--,2019年12月1日)
“
pack-objects
: 避免无意义的
oe_map_new_pack()
调用
签署者:Jeff King
审核者:Derrick Stolee”
自从
43fa44fa3b(pack-objects:将
in_pack
移出结构体
object_entry,
2018-04-14)以来,我们使用了一个复杂的系统来保存一些每个对象的内存。每个
object_entry
结构都有一个10位字段用于存储它所在的包的索引。我们使用
packing_data->in_pack_by_idx
将这些索引映射到指针上,在程序开始时对其进行初始化。如果我们有2^10或更多的包,则创建一个包指针数组,每个对象一个。这是
packing_data->in_pack
。如果在我们初始化
in_pack_by_idx
之后有新的包到达,它还没有索引。我们通过调用
oe_map_new_pack()
解决这个问题,它会动态切换到不太优化的
in_pack
机制,分配数组并为已经看到的对象回填它。但是,即使我们已经切换到这种机制(因为我们确实看到了一个新包,或者因为我们一开始就有太多的包),这个逻辑也会触发。结果不会产生错误的结果,但非常慢。发生的情况是:假设你有一个包含500k个对象和2000个包的仓库,你想重新打包。在查看任何对象之前,我们调用
prepare_in_pack_by_idx()
。它开始为每个包分配索引。在第1024个包之前,它发现有太多的包,于是退出,将
in_pack_by_idx
作为
NULL
。在实际添加对象到打包列表时,我们调用
oe_set_in_pack()
,它检查包是否已经有了索引。如果是第1023个后的包,则没有索引,我们将调用
oe_map_new_pack()
。但是这个函数没有什么有用的工作要做。我们已经使用
in_pack
,所以它只是毫无意义地遍历完整个对象列表,尝试回填
in_pack
。我们最终会为近1000个包(每个包可能被一个以上的对象触发)执行此操作。每次触发时,我们可能会迭代超过500k个对象。因此,在绝对最坏的情况下,这是对象数量的平方级别。解决方案很简单:如果我们已经转换为使用
in_pack
,则不需要再检查包是否具有索引,因为根据定义,我们不会使用它。因此,我们可以将“包是否具有有效索引”的检查推入条件语句的一半中,我们知道我们将使用它的地方。当前的p5303测试不幸地没有注意到这个问题,因为它最多只能达到1000个包。如果我们在2000个包时添加一个新测试,它会显示改进:
Test HEAD^ HEAD
> ----------------------------------------------------------------------
5303.12: repack (2000) 26.72(39.68+0.67) 15.70(28.70+0.66) -41.2%
However, these many-pack test cases are rather expensive to run, so adding larger and larger numbers isn't appealing. Instead, we can show it off more easily by using GIT_TEST_FULL_IN_PACK_ARRAY,
which forces us into the absolute worst case: no pack has an index, so we'll trigger oe_map_new_pack()
pointlessly for every single object, making it truly quadratic.
Here are the numbers (on git.git) with the included change to p5303:
Test HEAD^ HEAD
> ----------------------------------------------------------------------
5303.3: rev-list (1) 2.05(1.98+0.06) 2.06(1.99+0.06) +0.5%
5303.4: repack (1) 33.45(33.46+0.19) 2.75(2.73+0.22) -91.8%
5303.6: rev-list (50) 2.07(2.01+0.06) 2.06(2.01+0.05) -0.5%
5303.7: repack (50) 34.21(35.18+0.16) 3.49(4.50+0.12) -89.8%
5303.9: rev-list (1000) 2.87(2.78+0.08) 2.88(2.80+0.07) +0.3%
5303.10: repack (1000) 41.26(51.30+0.47) 10.75(20.75+0.44) -73.9%
Again, those improvements aren't realistic for the 1-pack case (because in the real world, the full-array solution doesn't kick in), but it's more useful to be testing the more-complicated code path.
While we're looking at this issue, we'll tweak one more thing: in oe_map_new_pack()
, we call REALLOC_ARRAY(pack->in_pack)
. But we'd never expect to get here unless we're back-filling it for the first time, in which case it would be NULL
.
So let's switch that to ALLOC_ARRAY()
for clarity, and add a BUG() to document the expectation. Unfortunately this code isn't well-covered in the test suite because it's inherently racy (it only kicks in if somebody else adds a new pack while we're in the middle of repacking).
pack.sparse
配置来提高推送性能(截至2019年第一季度)。这将被推广到所有平台。详情请参见《git push is very slow for a huge repo》。 - VonCgit -c push.useBitmaps=false push
,请参考。 - VonC