commit-reach: replace queue_has_nonstale() scan with O(1) tracking

paint_down_to_common() and ahead_behind() call queue_has_nonstale() on every iteration to decide whether to continue the walk. queue_has_nonstale() performs a linear scan of the priority queue, making the overall walk O(n*m) where n is the number of commits walked and m is the queue size. Introduce 'struct nonstale_queue', a thin wrapper around prio_queue that maintains a 'max_nonstale' pointer — the lowest-priority (oldest) non-stale commit seen so far. When this commit is popped, every remaining queue entry is known to be stale, so the walk can stop. This reduces the per-iteration termination check from O(m) to O(1). Uses <= 0 (not < 0) when comparing priorities so that among distinct commits with equal priority (same generation and timestamp) the last-enqueued one is tracked. Since prio_queue breaks ties by insertion order, this ensures max_nonstale is always the last in its priority class to be popped, making pointer equality on pop sufficient for correctness. The previous commit's ENQUEUED deduplication guarantees each commit appears at most once in the queue, which is required for the pointer equality check to be unambiguous. On a large monorepo (3.7M commits), this yields ~2x end-to-end speedup for merge-base calculations on deep import branches. Profiling shows paint_down_to_common() drops from 50% to 4% of total runtime (~27x faster), with the remaining time in commit graph lookups and heap operations: Before: 8536ms / 5757ms / 4743ms (three test cases) After: 3956ms / 4383ms / 1927ms Suggested-by: Jeff King <peff@peff.net> Signed-off-by: Kristofer Karlsson <krka@spotify.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Kristofer Karlsson committed May 25, 2026 at 14:28 UTC a186b7797a8bd4b9ca09b9cb326a2dccee00f90e
1 file changed +65 -31
commit-reach.c
+65 -31
@@ -40,32 +40,62 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
40 return 0;
41 }
42
43 -static void prio_queue_put_dedup(struct prio_queue *queue, struct commit *c)
43 +/*
44 + * A prio_queue with O(1) termination check. 'max_nonstale' tracks
45 + * the lowest-priority non-stale commit enqueued so far; once it is
46 + * popped, every remaining entry is known to be STALE.
47 + */
48 +struct nonstale_queue {
49 + struct prio_queue pq;
50 + struct commit *max_nonstale;
51 +};
52 +
53 +static void nonstale_queue_put(struct nonstale_queue *queue,
54 + struct commit *c)
55 +{
56 + struct commit *old = queue->max_nonstale;
57 +
58 + prio_queue_put(&queue->pq, c);
59 + if (c->object.flags & STALE)
60 + return;
61 + if (!old || queue->pq.compare(old, c, queue->pq.cb_data) <= 0)
62 + queue->max_nonstale = c;
63 +}
64 +
65 +static struct commit *nonstale_queue_get(struct nonstale_queue *queue)
66 +{
67 + struct commit *commit = prio_queue_get(&queue->pq);
68 +
69 + if (commit == queue->max_nonstale)
70 + queue->max_nonstale = NULL;
71 +
72 + return commit;
73 +}
74 +
75 +static void clear_nonstale_queue(struct nonstale_queue *queue)
76 +{
77 + clear_prio_queue(&queue->pq);
78 + queue->max_nonstale = NULL;
79 +}
80 +
81 +static void nonstale_queue_put_dedup(struct nonstale_queue *queue,
82 + struct commit *c)
83 {
84 if (c->object.flags & ENQUEUED)
85 return;
86 c->object.flags |= ENQUEUED;
48 - prio_queue_put(queue, c);
87 + nonstale_queue_put(queue, c);
88 }
89
51 -static struct commit *prio_queue_get_dedup(struct prio_queue *queue)
90 +static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue)
91 {
53 - struct commit *commit = prio_queue_get(queue);
92 + struct commit *commit = nonstale_queue_get(queue);
93 +
94 if (commit)
95 commit->object.flags &= ~ENQUEUED;
96 return commit;
97 }
98
59 -static int queue_has_nonstale(struct prio_queue *queue)
60 -{
61 - for (size_t i = 0; i < queue->nr; i++) {
62 - struct commit *commit = queue->array[i].data;
63 - if (!(commit->object.flags & STALE))
64 - return 1;
65 - }
66 - return 0;
67 -}
68 -
99 /* all input commits in one and twos[] must have been parsed! */
100 static int paint_down_to_common(struct repository *r,
101 struct commit *one, int n,
@@ -74,28 +104,30 @@ static int paint_down_to_common(struct repository *r,
104 int ignore_missing_commits,
105 struct commit_list **result)
106 {
77 - struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
107 + struct nonstale_queue queue = {
108 + { compare_commits_by_gen_then_commit_date }
109 + };
110 int i;
111 timestamp_t last_gen = GENERATION_NUMBER_INFINITY;
112 struct commit_list **tail = result;
113
114 if (!min_generation && !corrected_commit_dates_enabled(r))
83 - queue.compare = compare_commits_by_commit_date;
115 + queue.pq.compare = compare_commits_by_commit_date;
116
117 one->object.flags |= PARENT1;
118 if (!n) {
119 commit_list_append(one, result);
120 return 0;
121 }
90 - prio_queue_put_dedup(&queue, one);
122 + nonstale_queue_put_dedup(&queue, one);
123
124 for (i = 0; i < n; i++) {
125 twos[i]->object.flags |= PARENT2;
94 - prio_queue_put_dedup(&queue, twos[i]);
126 + nonstale_queue_put_dedup(&queue, twos[i]);
127 }
128
97 - while (queue_has_nonstale(&queue)) {
98 - struct commit *commit = prio_queue_get_dedup(&queue);
129 + while (queue.max_nonstale) {
130 + struct commit *commit = nonstale_queue_get_dedup(&queue);
131 struct commit_list *parents;
132 int flags;
133 timestamp_t generation = commit_graph_generation(commit);
@@ -125,7 +157,7 @@ static int paint_down_to_common(struct repository *r,
157 if ((p->object.flags & flags) == flags)
158 continue;
159 if (repo_parse_commit(r, p)) {
128 - clear_prio_queue(&queue);
160 + clear_nonstale_queue(&queue);
161 commit_list_free(*result);
162 *result = NULL;
163 /*
@@ -141,11 +173,11 @@ static int paint_down_to_common(struct repository *r,
173 oid_to_hex(&p->object.oid));
174 }
175 p->object.flags |= flags;
144 - prio_queue_put_dedup(&queue, p);
176 + nonstale_queue_put_dedup(&queue, p);
177 }
178 }
179
148 - clear_prio_queue(&queue);
180 + clear_nonstale_queue(&queue);
181 commit_list_sort_by_date(result);
182 return 0;
183 }
@@ -1039,11 +1071,11 @@ struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from,
1071 define_commit_slab(bit_arrays, struct bitmap *);
1072 static struct bit_arrays bit_arrays;
1073
1042 -static void insert_no_dup(struct prio_queue *queue, struct commit *c)
1074 +static void insert_no_dup(struct nonstale_queue *queue, struct commit *c)
1075 {
1076 if (c->object.flags & PARENT2)
1077 return;
1046 - prio_queue_put(queue, c);
1078 + nonstale_queue_put(queue, c);
1079 c->object.flags |= PARENT2;
1080 }
1081
@@ -1068,7 +1100,9 @@ void ahead_behind(struct repository *r,
1100 struct commit **commits, size_t commits_nr,
1101 struct ahead_behind_count *counts, size_t counts_nr)
1102 {
1071 - struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date };
1103 + struct nonstale_queue queue = {
1104 + { .compare = compare_commits_by_gen_then_commit_date }
1105 + };
1106 size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD);
1107
1108 if (!commits_nr || !counts_nr)
@@ -1091,8 +1125,8 @@ void ahead_behind(struct repository *r,
1125 insert_no_dup(&queue, c);
1126 }
1127
1094 - while (queue_has_nonstale(&queue)) {
1095 - struct commit *c = prio_queue_get(&queue);
1128 + while (queue.max_nonstale) {
1129 + struct commit *c = nonstale_queue_get(&queue);
1130 struct commit_list *p;
1131 struct bitmap *bitmap_c = get_bit_array(c, width);
1132
@@ -1134,10 +1168,10 @@ void ahead_behind(struct repository *r,
1168
1169 /* STALE is used here, PARENT2 is used by insert_no_dup(). */
1170 repo_clear_commit_marks(r, PARENT2 | STALE);
1137 - for (size_t i = 0; i < queue.nr; i++)
1138 - free_bit_array(queue.array[i].data);
1171 + for (size_t i = 0; i < queue.pq.nr; i++)
1172 + free_bit_array(queue.pq.array[i].data);
1173 clear_bit_arrays(&bit_arrays);
1140 - clear_prio_queue(&queue);
1174 + clear_nonstale_queue(&queue);
1175 }
1176
1177 struct commit_and_index {