From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-03 05:49:50
This series depends on en/merge-ort-perf.
For the very curious who are wondering about the first five optimization
batches; see the end of this email.
This series makes full use of exact renames; see commit messages for
details. It represents "Optimization #1" from my Git Merge 2020 talk[1]. For
the testcases mentioned in commit 557ac0350d ("merge-ort: begin performance
work; instrument with trace2_region_* calls", 2020-10-28), the changes in
just this series improves the performance as follows:
Before Series After Series
no-renames: 14.263 s ± 0.053 s 13.815 s ± 0.062 s
mega-renames: 5504.231 s ± 5.150 s 1799.937 s ± 0.493 s
just-one-mega: 158.534 s ± 0.498 s 51.289 s ± 0.019 s
As a reminder, before any merge-ort/diffcore-rename performance work, the
performance results we started with (as noted in the same commit message)
were:
no-renames-am: 6.940 s ± 0.485 s
no-renames: 18.912 s ± 0.174 s
mega-renames: 5964.031 s ± 10.459 s
just-one-mega: 149.583 s ± 0.751 s
[1]
https://github.com/newren/presentations/blob/pdfs/merge-performance/merge-performance-slides.pdf
=== Previous optimization batches ===
I'm labeling this as the "6th" batch, due to other optimizations submitted
previously, and a number of optimizations baked into the design of
fast-rebase and merge-ort.
1. Previously submitted hashmap/strmap optimizations 1a) 33f20d8217
(hashmap: introduce a new hashmap_partial_clear()) 1b) 6ccdfc2a20
(strmap: enable faster clearing and reusing of strmaps) 1c) a208ec1f0b
(strmap: enable allocations to come from a mem_pool) 1d) 23a276a9c4
(strmap: take advantage of FLEXPTR_ALLOC_STR when relevant)
2. Previously submitted diffcore-rename optimizations 2a) b970b4ef62
(diffcore-rename: simplify and accelerate register_rename_src()) 2b)
9db2ac5616 (diffcore-rename: accelerate rename_dst setup) 2c) 350410f6b1
(diffcore-rename: remove unnecessary duplicate entry checks)
3. fast-rebase optimizations 3a) Avoid updating working-tree/index with
every intermediate patch 3b) avoid reading/writing rebase metadata until
conflict or completion
4. Small stuff baked into merge-ort design 4a) Using pahole to note I can
reduce size of merged_info by 8 bytes 4b) Avoid recomparing hashes (due
to use of match_masks) 4c) Avoid unconditional dropping and re-reading
of the index 4d) avoid checking index matches HEAD with every patch; do
it at start only
5. Big stuff baked into merge-ort design 5a) Avoid quadratic behavior with
O(N) insertions/removals of index entries 5b) Avoid numerous expensive
mini-tree traversals done by merge-recursive 5c) Avoid recursing into
trees where both sides match merge base
Elijah Newren (2):
diffcore-rename: no point trying to find a match better than exact
diffcore-rename: filter rename_src list when possible
diffcore-rename.c | 69 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 62 insertions(+), 7 deletions(-)
base-commit: 557ac0350d9efa1f59c708779ca3fb3aee121131
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-842%2Fnewren%2Fort-perf-batch-6-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-842/newren/ort-perf-batch-6-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/842
--
gitgitgadget
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-03 05:49:51
From: Elijah Newren <redacted>
diffcore_rename() had some code to avoid having destination paths that
already had an exact rename detected from being re-checked for other
renames. Source paths, however, were re-checked because we wanted to
allow the possibility of detecting copies. But if copy detection isn't
turned on, then this merely amounts to attempting to find a
better-than-exact match, which naturally ends up being an expensive
no-op. In particular, copy detection is never turned on by the merge
machinery.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.263 s ± 0.053 s 14.119 s ± 0.101 s
mega-renames: 5504.231 s ± 5.150 s 1802.044 s ± 0.828 s
just-one-mega: 158.534 s ± 0.498 s 51.391 s ± 0.028 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-03 05:49:59
From: Elijah Newren <redacted>
We have to look at each entry in rename_src a total of rename_dst_nr
times. When we're not detecting copies, any exact renames or ignorable
rename paths will just be skipped over. While checking that these can
be skipped over is a relatively cheap check, it's still a waste of time
to do that check more than once, let alone rename_dst_nr times. When
rename_src_nr is a few thousand times bigger than the number of relevant
sources (such as when cherry-picking a commit that only touched a
handful of files, but from a side of history that has different names
for some high level directories), this time can add up.
First make an initial pass over the rename_src array and move all the
relevant entries to the front, so that we can iterate over just those
relevant entries.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.119 s ± 0.101 s 13.815 s ± 0.062 s
mega-renames: 1802.044 s ± 0.828 s 1799.937 s ± 0.493 s
just-one-mega: 51.391 s ± 0.028 s 51.289 s ± 0.019 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 67 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 57 insertions(+), 10 deletions(-)
On 2/3/2021 12:49 AM, Elijah Newren via GitGitGadget wrote:
From: Elijah Newren <redacted>
diffcore_rename() had some code to avoid having destination paths that
already had an exact rename detected from being re-checked for other
renames. Source paths, however, were re-checked because we wanted to
allow the possibility of detecting copies. But if copy detection isn't
turned on, then this merely amounts to attempting to find a
better-than-exact match, which naturally ends up being an expensive
no-op. In particular, copy detection is never turned on by the merge
machinery.
Ok, delete the renamed files from the sources. Using a new variable
because rename_src_nr is actually a static global to diffcore-rename.c,
describing the number of entries in the rename_src table. This is
scary, but I think your new local is a good way to change the local
logic of this method without adjusting that global.
/* All done? */
- if (!num_destinations)
+ if (!num_destinations || !num_sources)
goto cleanup;
And add an extra quit condition which is very possible to hit.
Is it only hit when every "delete" is actually a rename?
Have we "consumed" this input? Skip over it. Good. And this is inside
a double-loop:
for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
...
for (j = 0; j < rename_src_nr; j++) {
Keeping rename_src_nr in the inner loop makes sense, but this new
'continue;' gives most of the speedup, I imagine.
This is a nice speedup for such a simple optimization.
Thanks,
-Stolee
On Wed, Feb 3, 2021 at 3:44 AM Derrick Stolee [off-list ref] wrote:
On 2/3/2021 12:49 AM, Elijah Newren via GitGitGadget wrote:
quoted
From: Elijah Newren <redacted>
diffcore_rename() had some code to avoid having destination paths that
already had an exact rename detected from being re-checked for other
renames. Source paths, however, were re-checked because we wanted to
allow the possibility of detecting copies. But if copy detection isn't
turned on, then this merely amounts to attempting to find a
better-than-exact match, which naturally ends up being an expensive
no-op. In particular, copy detection is never turned on by the merge
machinery.
Ok, delete the renamed files from the sources. Using a new variable
because rename_src_nr is actually a static global to diffcore-rename.c,
describing the number of entries in the rename_src table. This is
scary, but I think your new local is a good way to change the local
logic of this method without adjusting that global.
I thought about changing rename_src, rename_src_nr, rename_dst, and
rename_dst_nr to all be in some struct and make one of those on the
stack locally in diffcore_rename() and then pass that structure
around. Would be nice to get rid of more global state. But I've got
enough things in the queue that I never made the jump.
quoted
/* All done? */
- if (!num_destinations)
+ if (!num_destinations || !num_sources)
goto cleanup;
And add an extra quit condition which is very possible to hit.
Is it only hit when every "delete" is actually a rename?
Right, when every "delete" gets paired by exact rename detection to
some "add" and is marked as a rename, meaning we have no more
"deletes" to pair with anything. In later series, there will be
additional reasons for num_sources to decrease and possibly hit 0.
Have we "consumed" this input? Skip over it. Good. And this is inside
a double-loop:
for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
...
for (j = 0; j < rename_src_nr; j++) {
Keeping rename_src_nr in the inner loop makes sense, but this new
'continue;' gives most of the speedup, I imagine.
This is a nice speedup for such a simple optimization.
Thanks,
-Stolee
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-03 20:04:39
From: Elijah Newren <redacted>
diffcore_rename() had some code to avoid having destination paths that
already had an exact rename detected from being re-checked for other
renames. Source paths, however, were re-checked because we wanted to
allow the possibility of detecting copies. But if copy detection isn't
turned on, then this merely amounts to attempting to find a
better-than-exact match, which naturally ends up being an expensive
no-op. In particular, copy detection is never turned on by the merge
machinery.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.263 s ± 0.053 s 14.119 s ± 0.101 s
mega-renames: 5504.231 s ± 5.150 s 1802.044 s ± 0.828 s
just-one-mega: 158.534 s ± 0.498 s 51.391 s ± 0.028 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-03 20:04:52
From: Elijah Newren <redacted>
We have to look at each entry in rename_src a total of rename_dst_nr
times. When we're not detecting copies, any exact renames or ignorable
rename paths will just be skipped over. While checking that these can
be skipped over is a relatively cheap check, it's still a waste of time
to do that check more than once, let alone rename_dst_nr times. When
rename_src_nr is a few thousand times bigger than the number of relevant
sources (such as when cherry-picking a commit that only touched a
handful of files, but from a side of history that has different names
for some high level directories), this time can add up.
First make an initial pass over the rename_src array and move all the
relevant entries to the front, so that we can iterate over just those
relevant entries.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.119 s ± 0.101 s 13.815 s ± 0.062 s
mega-renames: 1802.044 s ± 0.828 s 1799.937 s ± 0.493 s
just-one-mega: 51.391 s ± 0.028 s 51.289 s ± 0.019 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 57 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 51 insertions(+), 6 deletions(-)
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-14 07:36:05
This series makes full use of exact renames; removing not only a destination
pair, but a source pair as well when an exact rename is found and copy
detection is not turned on.
Changes since v2:
* Fix a comment typo, and fix a multi-line comment that didn't need to be a
multi-line comment
Elijah Newren (2):
diffcore-rename: no point trying to find a match better than exact
diffcore-rename: filter rename_src list when possible
diffcore-rename.c | 71 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 61 insertions(+), 10 deletions(-)
base-commit: f0117958910fbc734457a83a9f8ecc3c62463417
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-842%2Fnewren%2Fort-perf-batch-6-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-842/newren/ort-perf-batch-6-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/842
Range-diff vs v2:
1: 770e894b4abd = 1: a59c1960f614 diffcore-rename: no point trying to find a match better than exact
2: 7ae9460d3dba ! 2: dd6595b45640 diffcore-rename: filter rename_src list when possible
@@ diffcore-rename.c: static int find_renames(struct diff_score *mx, int dst_cnt, i
+ if (detecting_copies)
+ return; /* nothing to remove */
+ if (break_idx)
-+ return; /* culling incompatbile with break detection */
++ return; /* culling incompatible with break detection */
+
+ /*
+ * Note on reasons why we cull unneeded sources but not destinations:
@@ diffcore-rename.c: static int find_renames(struct diff_score *mx, int dst_cnt, i
{
int detect_rename = options->detect_rename;
@@ diffcore-rename.c: void diffcore_rename(struct diff_options *options)
+ if (minimum_score == MAX_SCORE)
goto cleanup;
- /*
+- /*
- * Calculate how many renames are left (but all the source
- * files still remain as options for rename/copies!)
-+ * Calculate how many renames are left
- */
+- */
++ /* Calculate how many renames are left */
num_destinations = (rename_dst_nr - rename_count);
+ remove_unneeded_paths_from_src(want_copies);
num_sources = rename_src_nr;
--
gitgitgadget
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-14 07:36:05
From: Elijah Newren <redacted>
diffcore_rename() had some code to avoid having destination paths that
already had an exact rename detected from being re-checked for other
renames. Source paths, however, were re-checked because we wanted to
allow the possibility of detecting copies. But if copy detection isn't
turned on, then this merely amounts to attempting to find a
better-than-exact match, which naturally ends up being an expensive
no-op. In particular, copy detection is never turned on by the merge
machinery.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.263 s ± 0.053 s 14.119 s ± 0.101 s
mega-renames: 5504.231 s ± 5.150 s 1802.044 s ± 0.828 s
just-one-mega: 158.534 s ± 0.498 s 51.391 s ± 0.028 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
From: Elijah Newren via GitGitGadget <hidden> Date: 2021-02-14 07:36:05
From: Elijah Newren <redacted>
We have to look at each entry in rename_src a total of rename_dst_nr
times. When we're not detecting copies, any exact renames or ignorable
rename paths will just be skipped over. While checking that these can
be skipped over is a relatively cheap check, it's still a waste of time
to do that check more than once, let alone rename_dst_nr times. When
rename_src_nr is a few thousand times bigger than the number of relevant
sources (such as when cherry-picking a commit that only touched a
handful of files, but from a side of history that has different names
for some high level directories), this time can add up.
First make an initial pass over the rename_src array and move all the
relevant entries to the front, so that we can iterate over just those
relevant entries.
For the testcases mentioned in commit 557ac0350d ("merge-ort: begin
performance work; instrument with trace2_region_* calls", 2020-10-28),
this change improves the performance as follows:
Before After
no-renames: 14.119 s ± 0.101 s 13.815 s ± 0.062 s
mega-renames: 1802.044 s ± 0.828 s 1799.937 s ± 0.493 s
just-one-mega: 51.391 s ± 0.028 s 51.289 s ± 0.019 s
Signed-off-by: Elijah Newren <redacted>
---
diffcore-rename.c | 59 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 51 insertions(+), 8 deletions(-)
@@ -454,6 +454,54 @@ static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, ireturncount;}+staticvoidremove_unneeded_paths_from_src(intdetecting_copies)+{+inti,new_num_src;++if(detecting_copies)+return;/* nothing to remove */+if(break_idx)+return;/* culling incompatible with break detection */++/*+*Noteonreasonswhywecullunneededsourcesbutnotdestinations:+*1)Pairingsarestoredinrename_dst(notrename_src),whichwe+*needtokeeparound.So,wejustcan'tcullrename_dsteven+*ifwewantedto.Butdoingsowouldn'thelpbecause...+*+*2)Thereisamatrixpairwisecomparisonthatfollowsthe+*"Performing inexact rename detection"progressmessage.+*Iteratingoverthedestinationsisdoneintheouterloop,+*henceweonlyiterateovereachofthoseonceandwecan+*easilyskiptheouterloopearlyifthedestinationisn't+*relevant.That'sonlyonecheckperdestinationpathto+*skip.+*+*Bycontrast,thesourcesareiteratedintheinnerloop;if+*wecheckwhetherasourcecanbeskipped,thenwe'llbe+*checkingitNseparatetimes,onceforeachdestination.+*Wedon'twanttohavetoiterateoverknown-not-needed+*sourcesNtimeseach,soavoidthatbyremovingthesources+*fromrename_srchere.+*/+for(i=0,new_num_src=0;i<rename_src_nr;i++){+/*+*renamesarestoredinrename_dst,soifarenamehas+*alreadybeendetectedusingthissource,wecanjust+*removethesourceknowingrename_dsthasitsinfo.+*/+if(rename_src[i].p->one->rename_used)+continue;++if(new_num_src<i)+memcpy(&rename_src[new_num_src],&rename_src[i],+sizeof(structdiff_rename_src));+new_num_src++;+}++rename_src_nr=new_num_src;+}+voiddiffcore_rename(structdiff_options*options){intdetect_rename=options->detect_rename;
@@ -529,14 +577,10 @@ void diffcore_rename(struct diff_options *options)if(minimum_score==MAX_SCORE)gotocleanup;-/*-*Calculatehowmanyrenamesareleft(butallthesource-*filesstillremainasoptionsforrename/copies!)-*/+/* Calculate how many renames are left */num_destinations=(rename_dst_nr-rename_count);+remove_unneeded_paths_from_src(want_copies);num_sources=rename_src_nr;-if(!want_copies)-num_sources-=rename_count;/* All done? */if(!num_destinations||!num_sources)