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 <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Dec 26, 2025 at 08:44 UTC abf05d856f50fbd8f0390b31e7187d78930dbaf5
2 files changed +27 -15
builtin/show-branch.c
+21 -13
@@ -18,6 +18,7 @@
18 #include "commit-slab.h"
19 #include "date.h"
20 #include "wildmatch.h"
21 +#include "prio-queue.h"
22
23 static const char*const show_branch_usage[] = {
24 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)
60 return "";
61 }
62
62 -static struct commit *interesting(struct commit_list *list)
63 +static struct commit *interesting(struct prio_queue *queue)
64 {
64 - while (list) {
65 - struct commit *commit = list->item;
66 - list = list->next;
65 + for (size_t i = 0; i < queue->nr; i++) {
66 + struct commit *commit = queue->array[i].data;
67 if (commit->object.flags & UNINTERESTING)
68 continue;
69 return commit;
@@ -222,17 +222,18 @@ static int mark_seen(struct commit *commit, struct commit_list **seen_p)
222 return 0;
223 }
224
225 -static void join_revs(struct commit_list **list_p,
225 +static void join_revs(struct prio_queue *queue,
226 struct commit_list **seen_p,
227 int num_rev, int extra)
228 {
229 int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
230 int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
231
232 - while (*list_p) {
232 + while (queue->nr) {
233 struct commit_list *parents;
234 - int still_interesting = !!interesting(*list_p);
235 - struct commit *commit = pop_commit(list_p);
234 + int still_interesting = !!interesting(queue);
235 + struct commit *commit = prio_queue_peek(queue);
236 + bool get_pending = true;
237 int flags = commit->object.flags & all_mask;
238
239 if (!still_interesting && extra <= 0)
@@ -253,8 +254,14 @@ static void join_revs(struct commit_list **list_p,
254 if (mark_seen(p, seen_p) && !still_interesting)
255 extra--;
256 p->object.flags |= flags;
256 - commit_list_insert_by_date(p, list_p);
257 + if (get_pending)
258 + prio_queue_replace(queue, p);
259 + else
260 + prio_queue_put(queue, p);
261 + get_pending = false;
262 }
263 + if (get_pending)
264 + prio_queue_get(queue);
265 }
266
267 /*
@@ -642,7 +649,8 @@ int cmd_show_branch(int ac,
649 {
650 struct commit *rev[MAX_REVS], *commit;
651 char *reflog_msg[MAX_REVS] = {0};
645 - struct commit_list *list = NULL, *seen = NULL;
652 + struct commit_list *seen = NULL;
653 + struct prio_queue queue = { compare_commits_by_commit_date };
654 unsigned int rev_mask[MAX_REVS];
655 int num_rev, i, extra = 0;
656 int all_heads = 0, all_remotes = 0;
@@ -886,14 +894,14 @@ int cmd_show_branch(int ac,
894 */
895 commit->object.flags |= flag;
896 if (commit->object.flags == flag)
889 - commit_list_insert_by_date(commit, &list);
897 + prio_queue_put(&queue, commit);
898 rev[num_rev] = commit;
899 }
900 for (i = 0; i < num_rev; i++)
901 rev_mask[i] = rev[i]->object.flags;
902
903 if (0 <= extra)
896 - join_revs(&list, &seen, num_rev, extra);
904 + join_revs(&queue, &seen, num_rev, extra);
905
906 commit_list_sort_by_date(&seen);
907
@@ -1004,7 +1012,7 @@ out:
1012 for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++)
1013 free(reflog_msg[i]);
1014 free_commit_list(seen);
1007 - free_commit_list(list);
1015 + clear_prio_queue(&queue);
1016 free(args_copy);
1017 free(head);
1018 return ret;
t/perf/p6010-merge-base.sh
+6 -2
@@ -83,9 +83,9 @@ build_history2 () {
83 test_expect_success 'setup' '
84 max_level=15 &&
85 build_history $max_level | git fast-import --export-marks=marks &&
86 - git tag one &&
86 + git branch one &&
87 build_history2 $max_level | git fast-import --import-marks=marks --force &&
88 - git tag two &&
88 + git branch two &&
89 git gc &&
90 git log --format=%H --no-merges >expect
91 '
@@ -98,4 +98,8 @@ test_expect_success 'verify result' '
98 test_cmp expect actual
99 '
100
101 +test_perf 'git show-branch' '
102 + git show-branch one two
103 +'
104 +
105 test_done