git push对于分支来说非常慢

时间:2015-03-18 09:55:03

标签: git

我们有一个相当大的git仓库(ios app资源)。我很欣赏git在使用它时会很慢,但如果我创建一个新分支并编辑几个文件(不是二进制文件)并推送,则需要永远。

感觉整个回购都被推了。我的印象是git只会发送差异,是不是错了? (我知道git存储整个文件的压缩版本,我的意思是我的分支和我从哪里分支的差异。)

如果我运行git diff --stat --cached origin/foo,那么我会看到一个简短的文件列表,看起来像我期望的那样,例如34 files changed, 1117 insertions(+), 72 deletions(-)。但当我推动它进入Writing objects: 21% (2317/10804)并停止时,好像它正在推动所有2.4GB的二进制数据。

我错过了什么(我用Google搜索了很多)?这是预期的行为吗?我在OS X(Mavericks)和ssh(git@github.com)上使用git 2.2.2。

我在这里找到了一个类似的问题:Git - pushing a remote branch for a large project is really slow但没有真正的答案。

2 个答案:

答案 0 :(得分:21)

您正在使用“智能”传输(这是一件好事),因此您可以获得增量,或者更具体地说,“增量压缩”。但这并不是说git会推动差异。

push和fetch在这里工作方式相同:在智能传输上,你的git调用远程,并且两端都有一个迷你对话,以确定谁拥有哪些存储库对象,由SHA-1标识并附加到特定标签(通常也是分支和标签名称,但也允许使用其他标签)。

例如,在这种情况下,你的git会打电话给他们并说:“我建议你将你的分支master设置为SHA-1 1234567...。我看到你的{{1} }目前是master,这是我认为您需要从333333...获得的内容。“他们应回答“好吧,我需要其中一些,但我已经......”。一旦你的git找到了需要发送的内容以及已经存在的内容,你的git会构建一个包含所有待发送对象的“瘦包” 1 。 (这是“使用最多%d个线程进行增量压缩”阶段。)

然后通过智能传输发送生成的薄包;这是您看到“编写对象”消息的位置。 (必须成功发送整个瘦包,然后使用7777777...再次将接收器“加长”并将其放入存储库。)

究竟发送什么数据取决于瘦包中的对象。那个应该只是“他们拥有的东西”和“你发送的内容”之间的提交集,以及这些提交所需的任何对象(树和blob),以及任何带注释的标签你是发送和那些他们尚未拥有的对象。

您可以使用git index-pack --fix-thin获取相关提交,以获取最新信息,然后使用git fetch查看您发送的提交内容。例如,如果您要在git rev-list上推送内容:

master

检查这些提交可能会显示一个非常大的二进制文件,该文件包含在其中一个中间文件中,然后在以后的提交中再次删除:

$ git fetch origin   # assuming the remote name is origin
[wait for it to finish]
$ git rev-list origin/master..master

如果一个提交有$ git log --name-status origin/master..master 然后后续(可能列在A giantfile.bin输出中的第一个)提交有git log,那么你可能会挂起发送{{1的blob }}

如果是这种情况,您可以使用D giantfile.bin来消除添加巨型二进制文件的提交,以便giantfile.bin不必发送该提交。

(如果您的历史记录是线性的 - 没有合并要推送 - 那么您也可以使用git rebase -i创建一系列包含补丁的电子邮件。这些适用于通过电子邮件发送给某人。其他网站 - 不是有人在github上等待接收它们,但你可以轻松检查补丁文件,看看它们中是否有巨大的数据。)


1 包是“瘦”的,因为它违反了普通的包文件规则,该规则要求任何增量压缩“下游”对象都在包本身中。相反,“下游”对象(实际上必须)可以在接收瘦包的存储库中。

答案 1 :(得分:0)

请注意,当您拥有超过1023个装箱时,Git 2.25修复了装箱对象中的极端减速问题。参见下面的数字。

这可能会对您的案件产生积极影响,因为您有大量的打包文件。

请参见commit f66e040Jeff King (peff)(2019年11月11日)。
(由Junio C Hamano -- gitster --commit 8faff38中合并,2019年12月1日)

  

pack-objects:避免毫无意义的oe_map_new_pack()通话

     

签名人:杰夫·金
  作者:Derrick Stolee

     

43fa44fa3b(打包对象:将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次包装(每个包装可能由一个以上的物体触发)。每次触发时,我们最多可以迭代50万个对象。因此,在绝对最坏的情况下,对象数量是二次方。

     

解决方案很简单:如果已经转换为使用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%
     

但是,这些多包测试用例运行起来相当昂贵,因此添加越来越多的用例并不吸引人。相反,我们可以通过使用GIT_TEST_FULL_IN_PACK_ARRAY,来更轻松地展示它,这将迫使我们进入绝对最坏的情况:没有包具有索引,因此我们将为每个对象无意义地触发oe_map_new_pack(),使其真正二次方。

     

以下是git.git上的数字,其中包括对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%
     

同样,对于1件装的包装盒来说,这些改进是不现实的(因为在现实世界中,无法使用全阵列解决方案),但是测试更复杂的代码路径更为有用。

     

我们正在研究此问题时,我们将进一步调整一件事:在oe_map_new_pack()中,我们称为REALLOC_ARRAY(pack->in_pack)。但是除非我们第一次重新填充它,否则我们永远不会期望到达这里,在这种情况下,它将是NULL
  因此,为清楚起见,我们将其切换为ALLOC_ARRAY(),并添加一个BUG()来记录期望值。不幸的是,该代码在测试套件中并未得到很好的覆盖,因为它本质上是不友好的(只有当其他人在重新包装过程中添加了新包装时,它才会生效)。