зеркало из https://github.com/microsoft/git.git
merge-ort: replace string_list_df_name_compare with faster alternative
Gathering accumulated times from trace2 output on the mega-renames
testcase, I saw the following timings (where I'm only showing a few
lines to highlight the portions of interest):
10.120 : label:incore_nonrecursive
4.462 : ..label:process_entries
3.143 : ....label:process_entries setup
2.988 : ......label:plist special sort
1.305 : ....label:processing
2.604 : ..label:collect_merge_info
2.018 : ..label:merge_start
1.018 : ..label:renames
In the above output, note that the 4.462 seconds for process_entries was
split as 3.143 seconds for "process_entries setup" and 1.305 seconds for
"processing" (and a little time for other stuff removed from the
highlight). Most of the "process_entries setup" time was spent on
"plist special sort" which corresponds to the following code:
trace2_region_enter("merge", "plist special sort", opt->repo);
plist.cmp = string_list_df_name_compare;
string_list_sort(&plist);
trace2_region_leave("merge", "plist special sort", opt->repo);
In other words, in a merge strategy that would be invoked by passing
"-sort" to either rebase or merge, sorting an array takes more time than
anything else. Serves me right for naming my merge strategy this way.
Rewrite the comparison function in a way that does not require finding
out the lengths of the strings when comparing them. While at it, tweak
the code for our specific case -- no need to handle a variety of modes,
for example. The combination of these changes reduced the time spent in
"plist special sort" by ~25% in the mega-renames case.
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: 5.622 s ± 0.059 s 5.235 s ± 0.042 s
mega-renames: 10.127 s ± 0.073 s 9.419 s ± 0.107 s
just-one-mega: 500.3 ms ± 3.8 ms 480.1 ms ± 3.9 ms
Signed-off-by: Elijah Newren <newren@gmail.com>
Reviewed-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
25e65b6dd5
Коммит
5a3743da32
67
merge-ort.c
67
merge-ort.c
|
@ -2746,31 +2746,58 @@ simple_cleanup:
|
|||
|
||||
/*** Function Grouping: functions related to process_entries() ***/
|
||||
|
||||
static int string_list_df_name_compare(const char *one, const char *two)
|
||||
static int sort_dirs_next_to_their_children(const char *one, const char *two)
|
||||
{
|
||||
int onelen = strlen(one);
|
||||
int twolen = strlen(two);
|
||||
unsigned char c1, c2;
|
||||
|
||||
/*
|
||||
* Here we only care that entries for D/F conflicts are
|
||||
* adjacent, in particular with the file of the D/F conflict
|
||||
* appearing before files below the corresponding directory.
|
||||
* The order of the rest of the list is irrelevant for us.
|
||||
* Here we only care that entries for directories appear adjacent
|
||||
* to and before files underneath the directory. We can achieve
|
||||
* that by pretending to add a trailing slash to every file and
|
||||
* then sorting. In other words, we do not want the natural
|
||||
* sorting of
|
||||
* foo
|
||||
* foo.txt
|
||||
* foo/bar
|
||||
* Instead, we want "foo" to sort as though it were "foo/", so that
|
||||
* we instead get
|
||||
* foo.txt
|
||||
* foo
|
||||
* foo/bar
|
||||
* To achieve this, we basically implement our own strcmp, except that
|
||||
* if we get to the end of either string instead of comparing NUL to
|
||||
* another character, we compare '/' to it.
|
||||
*
|
||||
* To achieve this, we sort with df_name_compare and provide
|
||||
* the mode S_IFDIR so that D/F conflicts will sort correctly.
|
||||
* We use the mode S_IFDIR for everything else for simplicity,
|
||||
* since in other cases any changes in their order due to
|
||||
* sorting cause no problems for us.
|
||||
* If this unusual "sort as though '/' were appended" perplexes
|
||||
* you, perhaps it will help to note that this is not the final
|
||||
* sort. write_tree() will sort again without the trailing slash
|
||||
* magic, but just on paths immediately under a given tree.
|
||||
*
|
||||
* The reason to not use df_name_compare directly was that it was
|
||||
* just too expensive (we don't have the string lengths handy), so
|
||||
* it was reimplemented.
|
||||
*/
|
||||
int cmp = df_name_compare(one, onelen, S_IFDIR,
|
||||
two, twolen, S_IFDIR);
|
||||
|
||||
/*
|
||||
* Now that 'foo' and 'foo/bar' compare equal, we have to make sure
|
||||
* that 'foo' comes before 'foo/bar'.
|
||||
* NOTE: This function will never be called with two equal strings,
|
||||
* because it is used to sort the keys of a strmap, and strmaps have
|
||||
* unique keys by construction. That simplifies our c1==c2 handling
|
||||
* below.
|
||||
*/
|
||||
if (cmp)
|
||||
return cmp;
|
||||
return onelen - twolen;
|
||||
|
||||
while (*one && (*one == *two)) {
|
||||
one++;
|
||||
two++;
|
||||
}
|
||||
|
||||
c1 = *one ? *one : '/';
|
||||
c2 = *two ? *two : '/';
|
||||
|
||||
if (c1 == c2) {
|
||||
/* Getting here means one is a leading directory of the other */
|
||||
return (*one) ? 1 : -1;
|
||||
} else
|
||||
return c1 - c2;
|
||||
}
|
||||
|
||||
static int read_oid_strbuf(struct merge_options *opt,
|
||||
|
@ -3489,7 +3516,7 @@ static void process_entries(struct merge_options *opt,
|
|||
trace2_region_leave("merge", "plist copy", opt->repo);
|
||||
|
||||
trace2_region_enter("merge", "plist special sort", opt->repo);
|
||||
plist.cmp = string_list_df_name_compare;
|
||||
plist.cmp = sort_dirs_next_to_their_children;
|
||||
string_list_sort(&plist);
|
||||
trace2_region_leave("merge", "plist special sort", opt->repo);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче