merge-ort: skip rename detection entirely if possible

diffcore_rename_extended() will do a bunch of setup, then check for exact renames, then abort before inexact rename detection if there are no more sources or destinations that need to be matched. It will sometimes be the case, however, that either * we start with neither any sources or destinations * we start with no *relevant* sources In the first of these two cases, the setup and exact rename detection will be very cheap since there are 0 files to operate on. In the second case, it is quite possible to have thousands of files with none of the source ones being relevant. Avoid calling diffcore_rename_extended() or even some of the setup before diffcore_rename_extended() when we can determine that rename detection is unnecessary. 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: 6.003 s ± 0.048 s 5.708 s ± 0.111 s mega-renames: 114.009 s ± 0.236 s 102.171 s ± 0.440 s just-one-mega: 3.489 s ± 0.017 s 3.471 s ± 0.015 s Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Mar 11, 2021 at 00:38 UTC f89b4f2beef515b583419b311665545b6c1dd948
1 file changed +44
merge-ort.c
+44
@@ -2157,6 +2157,19 @@ static int process_renames(struct merge_options *opt,
2157 return clean_merge;
2158 }
2159
2160 +static inline int possible_side_renames(struct rename_info *renames,
2161 + unsigned side_index)
2162 +{
2163 + return renames->pairs[side_index].nr > 0 &&
2164 + !strset_empty(&renames->relevant_sources[side_index]);
2165 +}
2166 +
2167 +static inline int possible_renames(struct rename_info *renames)
2168 +{
2169 + return possible_side_renames(renames, 1) ||
2170 + possible_side_renames(renames, 2);
2171 +}
2172 +
2173 static void resolve_diffpair_statuses(struct diff_queue_struct *q)
2174 {
2175 /*
@@ -2193,6 +2206,16 @@ static void detect_regular_renames(struct merge_options *opt,
2206 struct diff_options diff_opts;
2207 struct rename_info *renames = &opt->priv->renames;
2208
2209 + if (!possible_side_renames(renames, side_index)) {
2210 + /*
2211 + * No rename detection needed for this side, but we still need
2212 + * to make sure 'adds' are marked correctly in case the other
2213 + * side had directory renames.
2214 + */
2215 + resolve_diffpair_statuses(&renames->pairs[side_index]);
2216 + return;
2217 + }
2218 +
2219 repo_diff_setup(opt->repo, &diff_opts);
2220 diff_opts.flags.recursive = 1;
2221 diff_opts.flags.rename_empty = 0;
@@ -2310,6 +2333,8 @@ static int detect_and_process_renames(struct merge_options *opt,
2333 int need_dir_renames, s, clean = 1;
2334
2335 memset(&combined, 0, sizeof(combined));
2336 + if (!possible_renames(renames))
2337 + goto cleanup;
2338
2339 trace2_region_enter("merge", "regular renames", opt->repo);
2340 detect_regular_renames(opt, MERGE_SIDE1);
@@ -2344,6 +2369,25 @@ static int detect_and_process_renames(struct merge_options *opt,
2369 clean &= process_renames(opt, &combined);
2370 trace2_region_leave("merge", "process renames", opt->repo);
2371
2372 + goto simple_cleanup; /* collect_renames() handles some of cleanup */
2373 +
2374 +cleanup:
2375 + /*
2376 + * Free now unneeded filepairs, which would have been handled
2377 + * in collect_renames() normally but we skipped that code.
2378 + */
2379 + for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) {
2380 + struct diff_queue_struct *side_pairs;
2381 + int i;
2382 +
2383 + side_pairs = &renames->pairs[s];
2384 + for (i = 0; i < side_pairs->nr; ++i) {
2385 + struct diff_filepair *p = side_pairs->queue[i];
2386 + diff_free_filepair(p);
2387 + }
2388 + }
2389 +
2390 +simple_cleanup:
2391 /* Free memory for renames->pairs[] and combined */
2392 for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) {
2393 free(renames->pairs[s].queue);