内容简介:超越 GVFS: 更多 Git 大存储库的优化细节
在过去的几年中,微软一直将整个公司业务转移到基于 Visual Studio Team Services 的 现代工程系统 中,并使用 Git 作为版本控制系统。对于微软中的许多项目来说,这是没有问题的,因为: Git主页中如此描述:
Git 为 Linux 内核而生,这意味着它从一开始就必须能够有效地处理大规模代码仓库。
而且 Git 确实对 Linux 内核的项目进行了非常有效的处理,这对于一个开源项目来说确实是相当大规模的。它在 HEAD 中包含6万个文件,其历史记录跨度 12 年。
但对企业项目而言:6万个文件并不算多。
我所工作的存储仓库,其中包含了 Visual Studio Team Services 的代码库,拥有125,000个文件,比它的两倍还大。而在微软,这只能算是“中等规模”。当我们谈论一个 大 规模的源码树时,我们讨论的是:Windows,它的体量高达 350 万个文件。这包括创建 Windows 构建所需的源代码、测试和构建工具,以及创建了整个操作系统的 ISO。
这起初听起来很疯狂,但真的不用太过惊讶。对比下 Linux 内核,这 6 万个文件仅仅用于构建一个内核(和相关模块),该内核必须能够载入到你的机器的 RAM 中。我使用的内核 —— stack Ubuntu 16.04 镜像文件——是 7 兆字节,并附带另外 200 兆字节的模块数据。
Linus 曾开玩笑说内核已经变得“ 臃肿而庞大 ”。当然,这个 200 兆字节比 90 年代初期要大得多,当时内核不得不放到一个软盘上,而不得不为一个只有 4 兆字节 RAM 的机器适配,同时确保剩下足够的空间来驱动该系统。
而 Windows 10 呢? 那是 4GB 大小的 ISO 镜像。
由于所有Windows——内核、库,应用程序——是一起发布的,它们也被一起版本化,在一个大的“ monorepo ”中。 当计划将 Windows 迁移到 Git 时,我们考虑将代码库分解成许多较小的存储库,并将它们按照 Git 子模块或 Android 的 repo 系统进行分层处理。 但有时候 monrepos 是最简单的协作方式。
@xjoeduffyx 即使组件化有效,我还是去用 monorepo。高效协作至关重要: https://t.co/xt03PCGh3D
— Joe Duffy (@xjoeduffyx) February 3, 2017
不幸的是,像 Windows 这样庞大的 monorepo 有一个问题,Git 并没有处理好过如此大的存储库的先例。
在过去几年中,我们一直在努力调整 Git 来自适应处理像 Windows 存储库这样真正大型的 monorepos。这项工作的最大部分——到目前为止,是 GVFS,Git Virtual Filesystem(Git虚拟文件系统)。GVFS 允许我们的开发人员在 clone 时不用把 350 万个文件全部下载,而是在开发人员工作的源代码树的较小部分中进行页面访问。
Saeed Noursalehi 正在撰写一系列有关 GVFS 的文章,以及如何让我们让 Git 变得可伸缩。 这是非常高级的功能,绝对能够用于处理 Windows 大小规模的源代码树,但它并不全是我们在处理 Windows 存储时必须做的全部工作。将这么多文件放在单个存储库中对 Git 的数据结构和存储机制来说是很大的挑战,即使是在不是所有的文件都实际保存于工作目录中的情况下。
虽然 GVFS 是像 Windows 团队这样的巨型存储库的重要解决方案,但我们所做的这些额外工作将帮助常规的 Git 用户获得更多的标准存储库大小。
索引
索引(也称作“暂存区”或“缓存”)是 Git 仓库的核心数据结构之一。它包含仓库中每个文件的列表,并且几乎所有涉及工作目录的操作都会查阅它。索引将填充您在克隆仓库时以及切换分支时检出的路径列表。当您运行 status 以确定哪些文件处于 staged 和 modified 的状态,它将被审查。并且当您进行合并时,新的树(以及所有冲突)都将存储在索引中。
由于它用于这么多操作,所以访问索引必须很快,即使它包含 350 万个文件。Git 保持索引访问速度的一种方法是通过保存路径列表的排序,以便您可以通过二进制搜索来查找您需要的内容。
但是保存此列表的 排序 依然是有开销的。我们注意到大型存储库中的一个痛点是切换分支:这种日常的操作需要 30 秒到一分半钟不等。显然,检出操作中把文件传输到磁盘上的这个步骤是最慢的,不过如果我们再深入探究下去,会惊奇地发现,我们同样也花费了大量的时间去创建新的索引,以便它包含新分支中的文件列表。对于我们要插入索引的文件,我们会尝试找出我们要插入的文件。这意味着系统是通过对索引的二份查找来找到新路径的位置的。
在逻辑上充分说明,列表文件在我们插入时就 已经
被排序了。因此,我们会忙于在每个路径上执行一个 O(log n)
查找,这样做的目的只是为了去发现我们要在索引末尾附加的路径。 因此我们改变了这个步骤,跳过二分查找,只做追加。
这个看似很小的优化在 git checkout
调用中节省了 15-20% 的时间。事实证明,当 n 是 350 万个文件时, O(n log n)
变得相当缓慢。
当我们在查看索引时,我们观察到另一个类似的小操作:文件的校验和验证。当 Git 客户端编写索引时,它们计算其内容的 SHA-1 散列,并将其附加到文件的末尾。这使得 Git 在重新读取索引时会比较该哈希值,以确保它不会被微小的磁盘损坏所破坏。
对于小型的存储库和适用于 Linux 内核大小的存储库,这种计算基本上不是问题:计算 SHA-1 散列时读取索引耗时很低。但是对于像 Windows 这样的大型存储库,散列索引的内容几乎和解析它一样耗时。
我们首先将散列计算工作分解成后台线程,结果非常好。但是,最终,在每个操作上验证散列通常是不必要的,因为校验和检测到这种微妙的文件损坏是非常罕见的。 ( 虽然并不是完全没有听说过 )。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- MQTT Essential 细节笔记总结(深入理解MQTT细节)
- MetInfo 7.0.0 20200326 细节优化补丁,主要优化商城相关细节
- MetInfo7.0.0 20200407 细节优化补丁,修复编辑及手机端细节
- php 的小细节
- 2021 Raft 实现细节
- iOS键盘动画细节
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。