十多年后,使用 Git 2.31(2021 年第一季度),新的合并策略应该会有所帮助:ORT(“Ostensibly Recursive's Twin”)。
在重命名检测上包含了大量的性能优化工作,超越了简单的rename-threshold。
参见commit f78cf97、commit 07c9a7f、commit bd24aa2、commit da09f65、commit a35df33、commit f384525、commit 829514c(2021 年 2 月 14 日)和commit f15eb7c(2021 年 2 月 3 日)@9876 .
(由 Junio C Hamano -- gitster -- 合并于 commit 12bd175,2021 年 3 月 1 日)
例如:
签字人:Elijah Newren
我们希望在剩余的源文件和目标文件中使用唯一的基本名称来帮助通知重命名检测,以便可以首先检查更有可能的配对。
(如果在剩余的已删除和添加的文件中没有其他“foo.txt”文件,src/moduleA/foo.txt 和 source/module/A/foo.txt 可能相关。)
添加一个尚未使用的新函数,该函数创建 rename_src 和 rename_dst, 中的唯一基本名称的映射,以及 rename_src/rename_dst 中显示这些基本名称的索引。
非唯一的基本名称仍会显示在地图中,但索引无效 (-1)。
这个函数的灵感来自于在现实世界的存储库中,文件经常在不改变名称的情况下跨目录移动。
以下是一些示例存储库及其历史重命名(截至 2020 年初)保留基本名称的百分比:
- Linux:76%
- GCC:64%
- 壁虎:79%
- webkit:89%
这些统计数据本身并不能证明该领域的优化会有所帮助或有多大帮助,因为还有不成对的添加和删除、对我们考虑的基名的限制等,但它确实激发了这个想法在这方面尝试一下。
还有:
签字人:Elijah Newren
在现实世界的存储库中,大多数文件重命名不更改文件的基本名称并不罕见;即
大多数“重命名”只是将文件移动到不同的目录。
我们可以利用这一点来避免将所有重命名源候选者与所有重命名目标候选者进行比较,方法是首先将源与具有相同基本名称的目标进行比较。
- 如果两个具有相同基本名称的文件足够相似,我们记录重命名;
- 如果没有,我们会将这些文件包含在更详尽的矩阵比较中。
这意味着我们正在添加一组初步的附加比较,但对于每个文件,我们最多只将其与另一个文件进行比较。
例如,如果有一个 include/media/device.h 被删除和一个 src/module/media/device.h 被添加,并且在精确重命名检测后剩余的添加和删除文件集中没有其他 device.h 文件,那么这些两个文件将在初步步骤中进行比较。
这个提交实际上并没有使用这个新的优化,它只是添加了一个可以用于这个目的的函数。
请注意,与不进行优化相比,这种优化可能会给我们带来不同的结果,因为尽管具有相同基本名称的文件足够相似而被视为重命名,但没有相同基本名称的文件之间可能会更好地匹配。
我认为这是可以的,原因有四个:
- (1) 很容易向用户解释发生了什么,如果它确实发生了(或者甚至让他们直观地弄清楚),
- (2) 下一个补丁将显示它提供了如此大的性能提升,值得权衡,并且
- (3) 尽管具有唯一匹配的基本名称,但其他文件不太可能作为更好的匹配项。
理由(4)用整段来解释……
如果前三个原因还不够,请考虑重命名检测已经做了什么。
中断检测不是默认设置,这意味着如果文件具有相同的_fullname_,,那么即使它们相似度为 0%,它们也被认为是相关的。
事实上,在这种情况下,我们甚至不会费心比较文件来查看它们是否相似,更不用说将它们与所有其他文件进行比较以查看它们最相似的部分。
基本上,我们会根据足够的文件名相似性来覆盖内容相似性。
如果没有文件名相似度(目前实现为文件名的精确匹配),我们将钟摆向相反方向摆动,并说文件名相似度无关紧要,并比较源和目标的完整 N x M 矩阵以找出最相似的内容.
这种优化只是增加了另一种形式的文件名相似性比较,但也增加了文件内容相似性检查。
基本上,如果两个文件具有相同的基本名称并且足够相似以被视为重命名,则将它们标记为这样,而不会将这两个文件与所有其他重命名候选者进行比较。
这导致:
签字人:Elijah Newren
利用最后两个补丁中添加的新 find_basename_matches() 函数,在我们可以根据基本名称匹配文件的情况下更快地找到重命名。
作为一个快速提醒(有关更多详细信息,请参阅最后两条提交消息),这意味着例如,如果在添加的其他地方没有剩余的“extensions.txt”文件,docs/extensions.txt 和 docs/config/extensions.txt 被视为可能重命名和删除的文件,如果相似性检查确认它们相似,则将它们标记为重命名,而无需在其他文件中寻找更好的相似性匹配。
这是一种行为变化,在之前的提交消息中有更详细的介绍。
我们不会将此启发式方法与中断或复制检测一起使用。
断点检测是说文件名相似度并不意味着文件内容相似度,我们只想知道文件内容相似度。
复制检测的重点是使用更多资源来检查额外的相似性,而这是一种使用更少资源但也可能导致发现的相似性略少的优化。
因此,此优化背后的想法与这两个功能都背道而驰,并且将对这两个功能都关闭。
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654337 中列出@),此更改对性能的改进如下:
Before After
s ± 0.062 s 13.294 s ± 0.103 s
s ± 0.493 s 187.248 s ± 0.882 s
s ± 0.019 s 5.557 s ± 0.017 s
使用 Git 2.32(2021 年第二季度),重命名检测返工继续进行。
见commit 81afdf7、commit 333899e、commit 1ad69eb、commit b147301、commit b6e3d27、commit cd52e00、commit 0c4fd73、commit ae8cf74、commit ae8cf74、commit bde8b9f、commit 37a2514@@ (27) 987654348@.
(由 Junio C Hamano -- gitster -- 合并于 commit dd4048d,2021 年 3 月 22 日)
diffcore-rename:从 dir_rename_counts 计算 dir_rename_guess
审核人:Derrick Stolee
签字人:Elijah Newren
dir_rename_counts有一个映射的映射,特别是它有
old_dir => { new_dir => count }
我们想要一个简单的映射
old_dir => new_dir
基于给定old_dir 中哪个new_dir 的计数最高。
计算它并将其存储在dir_rename_guess。
这是我们猜测在基本名称不唯一时将文件移动到哪个目录所需的最后一块拼图。
(最后一张based on commit 37a2514)
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654355 中列出@),此更改对性能的改进如下:
Before After
s ± 0.062 s 12.596 s ± 0.061 s
s ± 0.284 s 130.465 s ± 0.259 s
s ± 0.019 s 3.958 s ± 0.010 s
仍然使用 Git 2.32(2021 年第二季度),通过跳过不相关的重命名优化了 ort 合并后端。
请参阅commit e4fd06e、commit f89b4f2、commit 174791f、commit 2fd9eda、commit a68e6ce、commit beb0614、commit 32a56df、commit 9799889(2021 年 3 月 11 日)Elijah Newren (newren)。
(由Junio C Hamano -- gitster -- 合并于commit 1b31224,2021 年 4 月 8 日)
签字人:Elijah Newren
diffcore_rename_extended() 将进行一系列设置,然后检查准确的重命名,如果没有更多需要匹配的源或目标,则在不准确的重命名检测之前中止。
然而,有时情况是这样的,要么
- 我们既不从任何来源或目的地开始
- 我们从没有相关来源开始>在这两种情况中的第一种情况下,设置和准确的重命名检测将非常便宜,因为有 0 个文件要操作。
在第二种情况下,很可能有数千个文件与源文件都不相关。
当我们可以确定不需要重命名检测时,避免调用diffcore_rename_extended() 甚至是diffcore_rename_extended() 之前的一些设置。
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654370 中列出@),此更改对性能的改进如下:
Before After
s ± 0.048 s 5.708 s ± 0.111 s
s ± 0.236 s 102.171 s ± 0.440 s
s ± 0.017 s 3.471 s ± 0.015 s
这项工作在 Git 2.33(2021 年第三季度)继续进行,其中优化了一系列合并操作中的重复重命名检测。
见commit 25e65b6,commit cbdca28,commit 86b41b3,commit d509802,commit 19ceb48,commit 64aceb6,commit 2734f2e,commit d29bd6d,commit d29bd6d,commit a22099f,commit f950026,@989 @(2021 年 5 月 20 日)和 commit 15f3e1e(2021 年 5 月 4 日)Elijah Newren (newren)。
(由 Junio C Hamano -- gitster -- 合并到 commit 169914e,2021 年 6 月 14 日)
签字人:Elijah Newren
当一系列提交的旧基数和新基数之间有许多重命名时,sequencer.c、merge-recursive.c 和 diffcore-rename.c 传统上拆分工作的方式导致重新检测相同的重命名每一个提交都被移植。
为了解决这个问题,最近的几次提交一直在创建重命名检测结果的缓存,确定在后续合并操作中何时可以安全使用此类缓存,添加辅助函数等等。
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654393 中列出@),此更改对性能的改进如下:
Before After
s ± 0.129 s 5.622 s ± 0.059 s
s ± 0.158 s 10.127 s ± 0.073 s
ms ± 6.1 ms 500.3 ms ± 3.8 ms
这是一个相当小的改进,但主要是因为之前的优化对这些特定的测试用例非常有效;此优化仅在其他优化不启动时才会启动。
如果我们取消 basename-guided rename detection 和 skip-irrelevant-renames 优化,那么我们会看到这个系列本身提高了性能,如下所示:
Before Basename Series After Just This Series
13.815 s ± 0.062 s 5.697 s ± 0.080 s
1799.937 s ± 0.493 s 205.709 s ± 0.457 s
由于此优化有助于加速以前的优化不适用的情况,最后的比较表明,这种缓存重命名优化有可能在不满足其他优化要求的情况下显着帮助有效。
使用 Git 2.33(2021 年第三季度),对“merge -sort”进行了更多修复和优化。
参见Elijah Newren (newren)commit ef68c3d、commit 356da0f、commit 61bf449、commit 5a3743d(2021 年 6 月 8 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 89efac8,2021 年 7 月 16 日)
merge-ort:用更快的替代品替换 string_list_df_name_compare
签字人:Elijah Newren
审核人:Derrick Stolee
以在比较字符串时不需要找出字符串长度的方式重写比较函数。
在此过程中,针对我们的具体情况调整代码——例如,无需处理各种模式。
这些更改的组合将在巨型重命名情况下花费在“plist 特殊排序”中的时间减少了约 25%。
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654404 中列出@),此更改对性能的改进如下:
Before After
s ± 0.059 s 5.235 s ± 0.042 s
s ± 0.073 s 9.419 s ± 0.107 s
ms ± 3.8 ms 480.1 ms ± 3.9 ms
而且,仍然使用 git 2.33:
请参阅commit 2bff554、commit 1aedd03、commit d331dd3、commit c75c423(2021 年 6 月 22 日)和commit 78cfdd0(2021 年 6 月 15 日)Elijah Newren (newren)。
(由@987654411 合并@in commit fdbcdfc,2021 年 7 月 16 日)
签字人:Elijah Newren
merge-ort 旨在最大限度地减少所需和使用的数据量,并对 diffcore-rename 进行了一些更改,以利用额外的元数据来实现数据最小化(特别是用于跳过“无关”的 relevant_sources 变量重命名)。
这一努力显然成功地大大减少了计算时间,但理论上也应该允许部分克隆下载更少的信息。
不过,以前,diffcore-rename 中使用的“prefetch”命令从未被修改过,并且下载了许多对于 merge-ort 来说不必要的 blob。
此提交纠正了这一点。
在进行基本名称比较时,我们只想获取将用于基本名称比较的对象。
如果在提取基本名称之后,我们没有更多相关的源(或没有更多的目标),那么我们就不需要进行完全不精确的重命名检测,并且可以跳过下载额外的源和目标文件。
即使我们必须在以后进行完全不精确的重命名检测,在基本名称匹配之后和完全不精确的重命名检测之前剔除不相关的源,所以我们仍然可以避免下载不相关源的 blob。
将prefetch() 重命名为inexact_prefetch(),并引入一个新的basename_prefetch() 以利用这一点。
如果我们从commit 557ac03修改测试用例(“merge-ort:开始性能工作;使用trace2_region_*调用的仪器”,2021-01-23,Git v2.31.0-rc0 -- merge列在@ 987654416@) 通过
--sparse --filter=blob:none
到clone 命令,并使用之前几次提交的新 trace2 "fetch_count" 输出来跟踪调用的 fetch 子命令的数量和在所有这些 fetch 中获取的对象数量,然后用于超重命名测试用例我们观察到以下内容:
在此提交之前,重新调整 35 个补丁:
strategy # of fetches total # of objects fetched
--------- ------------ --------------------------
recursive 62 11423
ort 30 11391
在这次提交之后,重新定位相同的 35 个补丁:
ort 32 63
这意味着新代码只需要下载少于 2 个 blob 的补丁即可。
这特别有趣,因为一开始存储库只下载了大约六个 TOTAL blob(因为仅使用顶级目录的默认稀疏签出)。
因此,对于这个特定的 linux 内核测试用例,上游端 (drivers/ -> pilots/) 涉及约 26,000 次重命名,其中 35 个补丁被重新定位,此更改减少了需要下载的 blob 数量约 180 倍。
也在 Git 2.33 中:
使用 Git 2.33(2021 年第三季度),进一步优化“合并排序”后端。
参见commit 8b09a90、commit 7bee6c1、commit 5e1ca57、commit e0ef578、commit d478f56、commit 528fc51、commit 785bf20(2021 年 7 月 16 日)Elijah Newren (newren)。
(合并Junio C Hamano -- gitster --commit 1a6fb01,2021 年 8 月 4 日)
merge-ort:使用缓存的重命名重新启动合并以降低进程进入成本
签字人:Elijah Newren
合并算法主要由以下三个函数组成:
collect_merge_info()
detect_and_process_renames()
process_entries()
在最后六次提交的微不足道的目录解析优化之前,process_entries() 一直是最慢的,其次是collect_merge_info(),然后是detect_and_process_renames()。
当应用微不足道的目录解析时,它通常会大大减少花费在两个较慢函数上的时间。
但是,盯着这个函数列表并注意到 process_entries() 是最昂贵的,并且知道如果我有缓存重命名可以避免它,这提出了一个简单的想法:更改
collect_merge_info()
detect_and_process_renames()
process_entries()
进入
collect_merge_info()
detect_and_process_renames()
<cache all the renames, and restart>
collect_merge_info()
detect_and_process_renames()
process_entries()
这可能看起来很奇怪,而且看起来需要更多的工作。
但是请注意,虽然我们运行了两次collect_merge_info(),但第二次我们使用普通目录解析,这使得它更快,所以collect_merge_info() 增加的时间很小。
当我们再次运行detect_and_process_renames() 时,所有重命名都被缓存了,所以它几乎是一个空操作(我们不调用diffcore_rename_extended(),但我们确实有一些数据结构检查和修复)。
最大的回报来自process_entries(),由于要处理的条目要少得多,因此速度会更快。
只有当我们可以将递归保存到足够多的目录中以使其值得我们花时间时,这种重新启动才有意义。
引入一个简单的启发式来指导这一点。
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654430 中列出@),此更改对性能的改进如下:
Before After
ms ± 3.8 ms 204.2 ms ± 3.0 ms
s ± 0.010 s 1.076 s ± 0.015 s
ms ± 3.9 ms 364.1 ms ± 7.0 ms
使用 Git 2.34(2021 年第四季度),最后一批“merge -sort”优化。
见commit 62a1516(2021 年 7 月 31 日)和 commit 092e511、commit f239fff、commit a8791ef、commit 6697ee0、commit 4137c54、commit cdf2241、commit fa0e936、@98760221@(7 月 30 日) by Elijah Newren (newren).
(由 Junio C Hamano -- gitster -- 合并于 commit 08ac213,2021 年 8 月 24 日)
merge-ort:将我们的 strmap 切换到使用内存池
签字人:Elijah Newren
对于作为clear_or_reinit_internal_opts() 的一部分无条件释放内存的所有strmaps(包括strintmaps 和strsets),将它们切换为使用我们的新内存池。。 p>
对于commit 557ac03 中提到的测试用例(“merge-ort:开始性能工作;使用trace2_region_* 调用的仪器”,2020-10-28,Git v2.31.0-rc0 -- merge 在@987654446 中列出@),此更改对性能的改进如下:
Before After
ms ± 3.2 ms 198.1 ms ± 2.6 ms
s ± 0.012 s 715.8 ms ± 4.0 ms
ms ± 3.9 ms 276.8 ms ± 4.2 ms