From: René Scharfe Date: Fri, 26 Dec 2025 07:44:28 +0000 (+0100) Subject: show-branch: use prio_queue X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=abf05d856f50fbd8f0390b31e7187d78930dbaf5;p=thirdparty%2Fgit.git show-branch: use prio_queue Building a list using commit_list_insert_by_date() has quadratic worst case complexity. Avoid it by using prio_queue. Use prio_queue_peek()+prio_queue_replace() instead of prio_queue_get()+ prio_queue_put() if possible, as the former only rebalance the prio_queue heap once instead of twice. In sane repositories this won't make much of a difference because the number of items in the list or queue won't be very high: Benchmark 1: ./git_v2.52.0 show-branch origin/main origin/next origin/seen origin/todo Time (mean ± σ): 538.2 ms ± 0.8 ms [User: 527.6 ms, System: 9.6 ms] Range (min … max): 537.0 ms … 539.2 ms 10 runs Benchmark 2: ./git show-branch origin/main origin/next origin/seen origin/todo Time (mean ± σ): 530.6 ms ± 0.4 ms [User: 519.8 ms, System: 9.8 ms] Range (min … max): 530.1 ms … 531.3 ms 10 runs Summary ./git show-branch origin/main origin/next origin/seen origin/todo ran 1.01 ± 0.00 times faster than ./git_v2.52.0 show-branch origin/main origin/next origin/seen origin/todo That number is not limited, though, and in pathological cases like the one in p6010 we see a sizable improvement: Test v2.52.0 HEAD ------------------------------------------------------------------ 6010.4: git show-branch 2.19(2.19+0.00) 0.03(0.02+0.00) -98.6% Signed-off-by: René Scharfe Signed-off-by: Junio C Hamano --- diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 441babf2e3..9e4efbaaed 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -18,6 +18,7 @@ #include "commit-slab.h" #include "date.h" #include "wildmatch.h" +#include "prio-queue.h" static const char*const show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -59,11 +60,10 @@ static const char *get_color_reset_code(void) return ""; } -static struct commit *interesting(struct commit_list *list) +static struct commit *interesting(struct prio_queue *queue) { - while (list) { - struct commit *commit = list->item; - list = list->next; + for (size_t i = 0; i < queue->nr; i++) { + struct commit *commit = queue->array[i].data; if (commit->object.flags & UNINTERESTING) continue; return commit; @@ -222,17 +222,18 @@ static int mark_seen(struct commit *commit, struct commit_list **seen_p) return 0; } -static void join_revs(struct commit_list **list_p, +static void join_revs(struct prio_queue *queue, struct commit_list **seen_p, int num_rev, int extra) { int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); - while (*list_p) { + while (queue->nr) { struct commit_list *parents; - int still_interesting = !!interesting(*list_p); - struct commit *commit = pop_commit(list_p); + int still_interesting = !!interesting(queue); + struct commit *commit = prio_queue_peek(queue); + bool get_pending = true; int flags = commit->object.flags & all_mask; if (!still_interesting && extra <= 0) @@ -253,8 +254,14 @@ static void join_revs(struct commit_list **list_p, if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; - commit_list_insert_by_date(p, list_p); + if (get_pending) + prio_queue_replace(queue, p); + else + prio_queue_put(queue, p); + get_pending = false; } + if (get_pending) + prio_queue_get(queue); } /* @@ -642,7 +649,8 @@ int cmd_show_branch(int ac, { struct commit *rev[MAX_REVS], *commit; char *reflog_msg[MAX_REVS] = {0}; - struct commit_list *list = NULL, *seen = NULL; + struct commit_list *seen = NULL; + struct prio_queue queue = { compare_commits_by_commit_date }; unsigned int rev_mask[MAX_REVS]; int num_rev, i, extra = 0; int all_heads = 0, all_remotes = 0; @@ -886,14 +894,14 @@ int cmd_show_branch(int ac, */ commit->object.flags |= flag; if (commit->object.flags == flag) - commit_list_insert_by_date(commit, &list); + prio_queue_put(&queue, commit); rev[num_rev] = commit; } for (i = 0; i < num_rev; i++) rev_mask[i] = rev[i]->object.flags; if (0 <= extra) - join_revs(&list, &seen, num_rev, extra); + join_revs(&queue, &seen, num_rev, extra); commit_list_sort_by_date(&seen); @@ -1004,7 +1012,7 @@ out: for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++) free(reflog_msg[i]); free_commit_list(seen); - free_commit_list(list); + clear_prio_queue(&queue); free(args_copy); free(head); return ret; diff --git a/t/perf/p6010-merge-base.sh b/t/perf/p6010-merge-base.sh index 54f52fa23e..08212dd037 100755 --- a/t/perf/p6010-merge-base.sh +++ b/t/perf/p6010-merge-base.sh @@ -83,9 +83,9 @@ build_history2 () { test_expect_success 'setup' ' max_level=15 && build_history $max_level | git fast-import --export-marks=marks && - git tag one && + git branch one && build_history2 $max_level | git fast-import --import-marks=marks --force && - git tag two && + git branch two && git gc && git log --format=%H --no-merges >expect ' @@ -98,4 +98,8 @@ test_expect_success 'verify result' ' test_cmp expect actual ' +test_perf 'git show-branch' ' + git show-branch one two +' + test_done