Skip to content

Commit 03771eb

Browse files
committed
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>
1 parent fc38c0f commit 03771eb

1 file changed

Lines changed: 65 additions & 31 deletions

File tree

commit-reach.c

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,32 +40,62 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
4040
return 0;
4141
}
4242

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)
4483
{
4584
if (c->object.flags & ENQUEUED)
4685
return;
4786
c->object.flags |= ENQUEUED;
48-
prio_queue_put(queue, c);
87+
nonstale_queue_put(queue, c);
4988
}
5089

51-
static struct commit *prio_queue_get_dedup(struct prio_queue *queue)
90+
static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue)
5291
{
53-
struct commit *commit = prio_queue_get(queue);
92+
struct commit *commit = nonstale_queue_get(queue);
93+
5494
if (commit)
5595
commit->object.flags &= ~ENQUEUED;
5696
return commit;
5797
}
5898

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-
6999
/* all input commits in one and twos[] must have been parsed! */
70100
static int paint_down_to_common(struct repository *r,
71101
struct commit *one, int n,
@@ -74,28 +104,30 @@ static int paint_down_to_common(struct repository *r,
74104
enum merge_base_flags mb_flags,
75105
struct commit_list **result)
76106
{
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+
};
78110
int i;
79111
timestamp_t last_gen = GENERATION_NUMBER_INFINITY;
80112
struct commit_list **tail = result;
81113

82114
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;
84116

85117
one->object.flags |= PARENT1;
86118
if (!n) {
87119
commit_list_append(one, result);
88120
return 0;
89121
}
90-
prio_queue_put_dedup(&queue, one);
122+
nonstale_queue_put_dedup(&queue, one);
91123

92124
for (i = 0; i < n; i++) {
93125
twos[i]->object.flags |= PARENT2;
94-
prio_queue_put_dedup(&queue, twos[i]);
126+
nonstale_queue_put_dedup(&queue, twos[i]);
95127
}
96128

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);
99131
struct commit_list *parents;
100132
int flags;
101133
timestamp_t generation = commit_graph_generation(commit);
@@ -133,7 +165,7 @@ static int paint_down_to_common(struct repository *r,
133165
if ((p->object.flags & flags) == flags)
134166
continue;
135167
if (repo_parse_commit(r, p)) {
136-
clear_prio_queue(&queue);
168+
clear_nonstale_queue(&queue);
137169
commit_list_free(*result);
138170
*result = NULL;
139171
/*
@@ -149,11 +181,11 @@ static int paint_down_to_common(struct repository *r,
149181
oid_to_hex(&p->object.oid));
150182
}
151183
p->object.flags |= flags;
152-
prio_queue_put_dedup(&queue, p);
184+
nonstale_queue_put_dedup(&queue, p);
153185
}
154186
}
155187

156-
clear_prio_queue(&queue);
188+
clear_nonstale_queue(&queue);
157189
commit_list_sort_by_date(result);
158190
return 0;
159191
}
@@ -1057,11 +1089,11 @@ struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from,
10571089
define_commit_slab(bit_arrays, struct bitmap *);
10581090
static struct bit_arrays bit_arrays;
10591091

1060-
static void insert_no_dup(struct prio_queue *queue, struct commit *c)
1092+
static void insert_no_dup(struct nonstale_queue *queue, struct commit *c)
10611093
{
10621094
if (c->object.flags & PARENT2)
10631095
return;
1064-
prio_queue_put(queue, c);
1096+
nonstale_queue_put(queue, c);
10651097
c->object.flags |= PARENT2;
10661098
}
10671099

@@ -1086,7 +1118,9 @@ void ahead_behind(struct repository *r,
10861118
struct commit **commits, size_t commits_nr,
10871119
struct ahead_behind_count *counts, size_t counts_nr)
10881120
{
1089-
struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date };
1121+
struct nonstale_queue queue = {
1122+
{ .compare = compare_commits_by_gen_then_commit_date }
1123+
};
10901124
size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD);
10911125

10921126
if (!commits_nr || !counts_nr)
@@ -1109,8 +1143,8 @@ void ahead_behind(struct repository *r,
11091143
insert_no_dup(&queue, c);
11101144
}
11111145

1112-
while (queue_has_nonstale(&queue)) {
1113-
struct commit *c = prio_queue_get(&queue);
1146+
while (queue.max_nonstale) {
1147+
struct commit *c = nonstale_queue_get(&queue);
11141148
struct commit_list *p;
11151149
struct bitmap *bitmap_c = get_bit_array(c, width);
11161150

@@ -1152,10 +1186,10 @@ void ahead_behind(struct repository *r,
11521186

11531187
/* STALE is used here, PARENT2 is used by insert_no_dup(). */
11541188
repo_clear_commit_marks(r, PARENT2 | STALE);
1155-
for (size_t i = 0; i < queue.nr; i++)
1156-
free_bit_array(queue.array[i].data);
1189+
for (size_t i = 0; i < queue.pq.nr; i++)
1190+
free_bit_array(queue.pq.array[i].data);
11571191
clear_bit_arrays(&bit_arrays);
1158-
clear_prio_queue(&queue);
1192+
clear_nonstale_queue(&queue);
11591193
}
11601194

11611195
struct commit_and_index {

0 commit comments

Comments
 (0)