git clone --depth=1 ...
2014 年的建议将在 2019 年第二季度使用 Git 2.22 变得更快。
这是因为,在初始的“ git clone --depth=...
”部分克隆期间,为枚举和跳过承诺对象(根据定义是从另一端获取的所有对象)的大部分连接检查花费周期是没有意义的。
这已经被优化了。
clone
:对部分克隆进行更快的对象检查
对于部分克隆,做一个完整的连接检查是浪费的;我们跳过承诺对象(对于部分克隆,它们是所有已知对象),并枚举它们以将它们从连接检查中排除在大型存储库上可能会花费大量时间。
最多,我们想确保我们得到任何想要的 refs 引用的对象。
对于部分克隆,只需检查这些对象是否已转移。
结果:
Test dfa33a2^ dfa33a2
-------------------------------------------------------------------------
5600.2: clone without blobs 18.41(22.72+1.09) 6.83(11.65+0.50) -62.9%
5600.3: checkout of result 1.82(3.24+0.26) 1.84(3.24+0.26) +1.1%
快 62%!
在 Git 2.26(2020 年第一季度)中,现在在部分克隆中禁用了不需要的连接检查。
请参阅Jonathan Tan ( ) 的提交 2df1aa2和提交 5003377(2020 年 1 月 12 日)。(由Junio C Hamano 合并 -- --在提交 8fb3945中,2020 年 2 月 14 日)jhowtan
gitster
签字人:Jonathan Tan
审核人:Jonathan Nieder
提交dfa33a298d (" clone
: do faster object check for partial clones", 2019-04-21, Git v2.22.0-rc0 -- merge ) 优化了克隆时完成的连通性检查,--filter
仅检查 refs 直接指向的对象是否存在.
但这还不够:它们还需要是承诺对象。
通过检查这些对象是否是 Promisor 对象(即它们出现在 Promisor 包中)来使此检查更加健壮。
和:
fetch
: 放弃全连接检查是否--filter
签字人:Jonathan Tan
审核人:Jonathan Nieder
如果指定了过滤器,我们不需要对刚刚获取的包文件的内容进行完整的连通性检查;我们只需要检查引用的对象是否是承诺对象。
这显着加快了对具有许多 Promisor 对象的存储库的获取速度,因为在连接检查期间,所有 Promisor 对象都被枚举(以将它们标记为 UNINTERESTING),这需要大量时间。
而且,仍然使用 Git 2.26(2020 年第一季度),对象可达性位图机制和部分克隆机制还没有准备好一起工作,因为部分克隆使用的一些对象过滤标准本质上依赖于对象遍历,但位图机制是绕过该对象遍历的优化。
然而,在某些情况下,他们可以一起工作,并且他们被教导过。
请参阅Junio C Hamano ( ) 的提交 20a5fd8(2020 年 2 月 18 日)。
参见提交3AB3185,提交84243DA,提交4F3BD56,提交CC4AA28 ,COC4AA28,提交2AAEB9A,6663AE0,提交4EB707E,commit EA047A8,commits 608d9c9 ,commit 55cb10f,commit 55cb10f,commit 55cb10f ,commit 792f811 ,commits d9020 and d90220,and d90220 , fe 9020 , fe 90,fe 90,fe。551cf8b(2020 年 2 月 13 日)作者:杰夫·金()gitster
peff
.
(由Junio C Hamano 合并 -- gitster
--在0df82d9 提交中,2020 年 3 月 2 日)
签字人:杰夫·金
就像之前实现的提交BLOB_NONE
一样,我们可以BLOB_LIMIT
通过查看结果中任何 blob 的大小并适当地取消设置它们的位来支持过滤器。
这比稍微贵一点,BLOB_NONE,
但仍然产生明显的加速(这些结果在git.git上):
Test HEAD~2 HEAD
------------------------------------------------------------------------------------
5310.9: rev-list count with blob:none 1.80(1.77+0.02) 0.22(0.20+0.02) -87.8%
5310.10: rev-list count with blob:limit=1k 1.99(1.96+0.03) 0.29(0.25+0.03) -85.4%
实现类似于BLOB_NONE
一个,除了我们必须在遍历 blob 类型位图时逐个对象(因为我们无法屏蔽匹配项,但必须单独查找每个 blob 的大小) .
using 的技巧ctz64()
来自show_objects_for_type()
,它同样需要找到单个位(但希望快速跳过没有 blob 的大块)。
Git 2.27(2020 年第二季度)将简化部分克隆存储库中的提交祖先连接性检查,其中假定“承诺”对象可以从承诺者远程存储库中按需延迟获取。
请参阅Jonathan Tan ( ) 的提交 2b98478(2020 年 3 月 20 日)。(由Junio C Hamano 合并 -- --在提交 0c60105中,2020 年 4 月 22 日)jhowtan
gitster
签字人:Jonathan Tan
审核人:Josh Steadmon
使用50033772d5 (" connected
: verify promisor-ness of partial clone", 2020-01-30, Git v2.26.0-rc0 -- merge in batch #5 ) 中的快速路径(检查承诺包)check_connected()
现在通过慢速路径 (rev-list) > - 如果所有要检查的对象都在承诺包中找到,则快速路径和慢速路径都会通过;
这意味着我们总是可以在需要执行慢速路径时尝试快速路径。
快速路径当前由一个标志保护;因此,删除该标志。
此外,使快速路径回退到慢速路径 - 如果快速路径失败,失败的 OID 和所有剩余的 OID 将被传递到 rev-list。
主要的用户可见的好处是从部分克隆中获取的性能 - 特别是在获取之前完成的连接检查的加速。
特别是,对我计算机上的部分克隆的无操作获取从 7 秒加快到 0.01 秒。这是对2df1aa239c中工作的补充(“ fetch
: forgo full connectivity check if --filter”, 2020-01-30, Git v2.26.0-rc0 -- merge在批次#5中列出),它是上述50033772d5。在该提交中,加快了获取后的连接检查。
在这些情况下,添加快速路径可能会导致性能下降:
我认为这些情况很少见,并且这种情况下的性能下降足够小(额外的对象数据库访问),避免标志的好处超过了这些。
在 Git 2.27(2020 年第 2 季度)中,带有对象过滤器“ --filter=tree:0
”的对象遍历现在可以利用可用的包位图。
请参阅Jeff King ( ) 的commit 9639474和commit 5bf7f1e(2020 年 5 月 4 日)。
请参阅Taylor Blau ( ) 的提交 b0a8d48和提交 856e12c(2020 年 5 月 4 日)。(由Junio C Hamano 合并 -- --在commit 69ae8ff中,2020 年 5 月 13 日)peff
ttaylorr
gitster
签字人:Taylor Blau
在上一个补丁中,我们可以轻松定义排除特定类型的所有对象的其他过滤器。--filter=tree:<n>
当 ' n
' 等于时,使用它来实现 ' ' 过滤器的位图级过滤0
。
位图对一般情况没有帮助,因为对于 ' n > 0
' 的值,对象过滤机制需要完整的树遍历才能确定给定树的深度。
缓存它也不是显而易见的,因为同一个树对象可以根据上下文具有不同的深度(例如,树在两次提交之间的目录层次结构中向上移动)。
但是, ' n = 0
' 案例可以得到帮助,而这个补丁就是这样做的。
在这棵树中运行p5310.11
并在带有内核的 master 上运行,我们可以看到这种情况有很大帮助:
Test master this tree
--------------------------------------------------------------------------------
5310.11: rev-list count with tree:0 10.68(10.39+0.27) 0.06(0.04+0.01) -99.4%
和:
请参阅Jeff King ( ) 的commit 9639474和commit 5bf7f1e(2020 年 5 月 4 日)。
请参阅Taylor Blau ( ) 的提交 b0a8d48和提交 856e12c(2020 年 5 月 4 日)。(由Junio C Hamano 合并 -- --在commit 69ae8ff中,2020 年 5 月 13 日)peff
ttaylorr
gitster
签字人:Jeff King
签字人:Taylor Blau
有时位图遍历仍然需要手动执行一些提交,因为这些提交不包含在位图包文件中(例如,由于自上次完全重新打包后的推送或提交)。
如果我们得到一个对象过滤器,我们不会将它传递给这个遍历。
正确性不是必需的,因为位图代码有自己的过滤器来对位图结果进行后处理(它必须过滤掉位图包文件中提到的对象)。
并且使用 blob 过滤器,也没有性能理由传递这些过滤器。填充遍历可以从结果中省略它们,但这不会为我们节省任何时间,因为我们仍然必须遍历每个树条目以查看它是否为 blob。
但是现在我们支持树过滤器,就有了节省的机会。过滤器tree:depth=0
意味着我们可以完全避免访问树,因为我们知道我们不会访问它们(或它们指向的任何子树或 blob)。
中的新测试p5310
显示了这一点(“部分位图”状态是一种状态HEAD~100
,它的祖先都在位图包中,但HEAD~100..HEAD
不是)。
以下是结果(针对linux.git
):
Test HEAD^ HEAD
-------------------------------------------------------------------------------------------------
[...]
5310.16: rev-list with tree filter (partial bitmap) 0.19(0.17+0.02) 0.03(0.02+0.01) -84.2%
节省的绝对数量并不大,但请记住,我们只省略了 100 个第一父链接(在linux.git
此处的版本中,即 894 个实际提交)。
在更病态的情况下,我们可能有更大比例的非位图提交。我没有费心在 perf 脚本中创建这样的案例,因为设置很昂贵,这足以显示节省的百分比。
在 Git 2.32(2021 年第 2 季度)中,对允许某些对象丢失和延迟检索的“承诺包”的处理已得到优化(有点)。
请参阅Jeff King ( )的提交 c1fa951、提交 45a187c、提交 fcc07e9(2021 年 4 月 13 日) 。(由Junio C Hamano 合并 -- --在提交 13158b9中,2021 年 4 月 30 日)peff
gitster
revision
: 避免使用 --exclude-promisor-objects 进行解析
签字人:杰夫·金
当--exclude-promisor-objects
给出时,在遍历任何对象之前,我们遍历任何 Promisor 包中的所有对象,将它们标记为 UNINTERESTING 和 SEEN。
我们将通过迭代包获得的 oid 转换为一个对象parse_object()
,但这有两个问题:
- 很慢;我们正在 zlib 膨胀(并从增量重构)packfile 中每个对象的每个字节
- 它将树缓冲区附加到它们的结构上,这意味着我们的堆使用量将增长以同时存储每个未压缩的树。
这可以是千兆字节。
我们显然可以通过在解析树缓冲区后释放它们来修复第二个问题。
但我们可以观察到该函数根本不查看对象内容!我们调用的唯一原因parse_object()
是我们需要一个“ struct object
”来设置标志。
这里有两个选项:
- 我们可以通过 查找对象类型
oid_object_info()
,然后调用相应的lookup_foo()
函数
- 我们可以调用
lookup_unknown_object()
,它为我们提供了一个OBJ_NONE
结构(稍后将object_as_type()
通过调用lookup_commit()
等自动转换)。
第一个更接近当前代码,但我们确实付出了代价来查找每个对象的类型。
后者在 CPU 中应该更高效,尽管它会浪费一点内存(“未知”对象结构是所有对象类型的联合,因此某些结构比它们需要的大)。
它还存在在lookup_object()
直接调用但尚未准备好处理的代码中触发潜在错误的风险OBJ_NONE
(这样的代码已经存在错误,但我们使用lookup_unknown_object()
的频率太低以至于它可能会被隐藏)。
我在这里选择了第二个选项。
我认为风险并不高(无论如何我们都希望找到并修复任何此类错误),并且总体上应该更有效。
p5600 中的新测试显示了改进(这是在git.git上):
Test HEAD^ HEAD
-------------------------------------------------------------------------------
5600.5: count commits 0.37(0.37+0.00) 0.38(0.38+0.00) +2.7%
5600.6: count non-promisor commits 11.74(11.37+0.37) 0.04(0.03+0.00) -99.7%
这个脚本的改进特别大,因为新克隆的部分 repo 中的每个对象都是一个承诺对象。
因此,在将它们全部标记后,就没有什么可遍历的了。