]> git.ipfire.org Git - thirdparty/git.git/commitdiff
merge-ort: replace string_list_df_name_compare with faster alternative
authorElijah Newren <newren@gmail.com>
Tue, 8 Jun 2021 16:11:39 +0000 (16:11 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 9 Jun 2021 02:40:03 +0000 (11:40 +0900)
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>
merge-ort.c

index ed21dc872436b63dcbff590726f9a85ce460bb8b..be80143f4db276872d3648af588c7556528f9afb 100644 (file)
@@ -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.
+        *
+        * 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.
         *
-        * 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.
+        * 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);