summaryrefslogtreecommitdiff
path: root/merge-ort.c
diff options
context:
space:
mode:
authorElijah Newren <newren@gmail.com>2021-06-08 16:11:39 (GMT)
committerJunio C Hamano <gitster@pobox.com>2021-06-09 02:40:03 (GMT)
commit5a3743da326b2e041c65ac12ba0d04a1fed45b58 (patch)
tree4b6db83ac3e0702164f970a86f88186fd571da1a /merge-ort.c
parent25e65b6dd52c987056f1cac00fe6073fbf8ea237 (diff)
downloadgit-5a3743da326b2e041c65ac12ba0d04a1fed45b58.zip
git-5a3743da326b2e041c65ac12ba0d04a1fed45b58.tar.gz
git-5a3743da326b2e041c65ac12ba0d04a1fed45b58.tar.bz2
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>
Diffstat (limited to 'merge-ort.c')
-rw-r--r--merge-ort.c67
1 files changed, 47 insertions, 20 deletions
diff --git a/merge-ort.c b/merge-ort.c
index ed21dc8..be80143 100644
--- a/merge-ort.c
+++ b/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.
+ *
+ * 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);