From 85a30b5b26c2953aa836c554af5fc58eed707e4a Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Mon, 25 May 2026 14:28:03 +0000 Subject: [PATCH 1/3] object.h: fix stale entries in object flag allocation table Update three stale entries found during an audit of the flag allocation table: - sha1-name.c was renamed to object-name.c - builtin/show-branch.c uses bits 0 and 2-28, not 0-26 (REV_SHIFT=2, MAX_REVS=FLAG_BITS-REV_SHIFT=27) - negotiator/skipping.c uses bits 2-5 like negotiator/default.c (ADVERTISED on bit 3 instead of COMMON_REF) Signed-off-by: Kristofer Karlsson Signed-off-by: Junio C Hamano --- object.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/object.h b/object.h index d814647ebe..2b26de3044 100644 --- a/object.h +++ b/object.h @@ -67,6 +67,7 @@ void object_array_init(struct object_array *array); * revision.h: 0---------10 15 23--------28 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 + * negotiator/skipping.c: 2--5 * walker.c: 0-2 * upload-pack.c: 4 11-----14 16-----19 * builtin/blame.c: 12-13 @@ -76,13 +77,13 @@ void object_array_init(struct object_array *array); * commit-graph.c: 15 * commit-reach.c: 16-----19 * builtin/last-modified.c: 1617 - * sha1-name.c: 20 + * object-name.c: 20 * list-objects-filter.c: 21 * bloom.c: 2122 * builtin/fsck.c: 0--3 * builtin/index-pack.c: 2021 * reflog.c: 10--12 - * builtin/show-branch.c: 0-------------------------------------------26 + * builtin/show-branch.c: 0-----------------------------------------------28 * builtin/unpack-objects.c: 2021 * pack-bitmap.h: 2122 */ From f767dae3e6c8359128d0ec83acd009751e92e419 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Mon, 25 May 2026 14:28:04 +0000 Subject: [PATCH 2/3] commit-reach: deduplicate queue entries in paint_down_to_common paint_down_to_common() can enqueue the same commit multiple times when it is reached through different parents with different flag combinations. Add an ENQUEUED flag to track whether a commit is currently in the priority queue, and skip it if already present. Introduce prio_queue_put_dedup() and prio_queue_get_dedup() wrappers that manage the ENQUEUED flag on enqueue and dequeue. This change is performance-neutral on its own: the O(n) queue_has_nonstale() scan still dominates the per-iteration cost. However, the deduplication guarantee (each commit appears in the queue at most once) is a prerequisite for the next commit, which replaces that scan with O(1) tracking. Signed-off-by: Kristofer Karlsson Signed-off-by: Junio C Hamano --- commit-reach.c | 27 ++++++++++++++++++++++----- object.h | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index d3a9b3ed6f..31e6110b13 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -17,8 +17,9 @@ #define PARENT2 (1u<<17) #define STALE (1u<<18) #define RESULT (1u<<19) +#define ENQUEUED (1u<<20) -static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT); +static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT | ENQUEUED); static int compare_commits_by_gen(const void *_a, const void *_b) { @@ -39,6 +40,22 @@ static int compare_commits_by_gen(const void *_a, const void *_b) return 0; } +static void prio_queue_put_dedup(struct prio_queue *queue, struct commit *c) +{ + if (c->object.flags & ENQUEUED) + return; + c->object.flags |= ENQUEUED; + prio_queue_put(queue, c); +} + +static struct commit *prio_queue_get_dedup(struct prio_queue *queue) +{ + struct commit *commit = prio_queue_get(queue); + if (commit) + commit->object.flags &= ~ENQUEUED; + return commit; +} + static int queue_has_nonstale(struct prio_queue *queue) { for (size_t i = 0; i < queue->nr; i++) { @@ -70,15 +87,15 @@ static int paint_down_to_common(struct repository *r, commit_list_append(one, result); return 0; } - prio_queue_put(&queue, one); + prio_queue_put_dedup(&queue, one); for (i = 0; i < n; i++) { twos[i]->object.flags |= PARENT2; - prio_queue_put(&queue, twos[i]); + prio_queue_put_dedup(&queue, twos[i]); } while (queue_has_nonstale(&queue)) { - struct commit *commit = prio_queue_get(&queue); + struct commit *commit = prio_queue_get_dedup(&queue); struct commit_list *parents; int flags; timestamp_t generation = commit_graph_generation(commit); @@ -124,7 +141,7 @@ static int paint_down_to_common(struct repository *r, oid_to_hex(&p->object.oid)); } p->object.flags |= flags; - prio_queue_put(&queue, p); + prio_queue_put_dedup(&queue, p); } } diff --git a/object.h b/object.h index 2b26de3044..8fb03ff90a 100644 --- a/object.h +++ b/object.h @@ -75,7 +75,7 @@ void object_array_init(struct object_array *array); * bundle.c: 16 * http-push.c: 11-----14 * commit-graph.c: 15 - * commit-reach.c: 16-----19 + * commit-reach.c: 16-------20 * builtin/last-modified.c: 1617 * object-name.c: 20 * list-objects-filter.c: 21 From a186b7797a8bd4b9ca09b9cb326a2dccee00f90e Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Mon, 25 May 2026 14:28:05 +0000 Subject: [PATCH 3/3] commit-reach: replace queue_has_nonstale() scan with O(1) tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Kristofer Karlsson Signed-off-by: Junio C Hamano --- commit-reach.c | 96 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/commit-reach.c b/commit-reach.c index 31e6110b13..2e86e1e803 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -40,32 +40,62 @@ static int compare_commits_by_gen(const void *_a, const void *_b) return 0; } -static void prio_queue_put_dedup(struct prio_queue *queue, struct commit *c) +/* + * A prio_queue with O(1) termination check. 'max_nonstale' tracks + * the lowest-priority non-stale commit enqueued so far; once it is + * popped, every remaining entry is known to be STALE. + */ +struct nonstale_queue { + struct prio_queue pq; + struct commit *max_nonstale; +}; + +static void nonstale_queue_put(struct nonstale_queue *queue, + struct commit *c) +{ + struct commit *old = queue->max_nonstale; + + prio_queue_put(&queue->pq, c); + if (c->object.flags & STALE) + return; + if (!old || queue->pq.compare(old, c, queue->pq.cb_data) <= 0) + queue->max_nonstale = c; +} + +static struct commit *nonstale_queue_get(struct nonstale_queue *queue) +{ + struct commit *commit = prio_queue_get(&queue->pq); + + if (commit == queue->max_nonstale) + queue->max_nonstale = NULL; + + return commit; +} + +static void clear_nonstale_queue(struct nonstale_queue *queue) +{ + clear_prio_queue(&queue->pq); + queue->max_nonstale = NULL; +} + +static void nonstale_queue_put_dedup(struct nonstale_queue *queue, + struct commit *c) { if (c->object.flags & ENQUEUED) return; c->object.flags |= ENQUEUED; - prio_queue_put(queue, c); + nonstale_queue_put(queue, c); } -static struct commit *prio_queue_get_dedup(struct prio_queue *queue) +static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue) { - struct commit *commit = prio_queue_get(queue); + struct commit *commit = nonstale_queue_get(queue); + if (commit) commit->object.flags &= ~ENQUEUED; return commit; } -static int queue_has_nonstale(struct prio_queue *queue) -{ - for (size_t i = 0; i < queue->nr; i++) { - struct commit *commit = queue->array[i].data; - if (!(commit->object.flags & STALE)) - return 1; - } - return 0; -} - /* all input commits in one and twos[] must have been parsed! */ static int paint_down_to_common(struct repository *r, struct commit *one, int n, @@ -74,28 +104,30 @@ static int paint_down_to_common(struct repository *r, int ignore_missing_commits, struct commit_list **result) { - struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; + struct nonstale_queue queue = { + { compare_commits_by_gen_then_commit_date } + }; int i; timestamp_t last_gen = GENERATION_NUMBER_INFINITY; struct commit_list **tail = result; if (!min_generation && !corrected_commit_dates_enabled(r)) - queue.compare = compare_commits_by_commit_date; + queue.pq.compare = compare_commits_by_commit_date; one->object.flags |= PARENT1; if (!n) { commit_list_append(one, result); return 0; } - prio_queue_put_dedup(&queue, one); + nonstale_queue_put_dedup(&queue, one); for (i = 0; i < n; i++) { twos[i]->object.flags |= PARENT2; - prio_queue_put_dedup(&queue, twos[i]); + nonstale_queue_put_dedup(&queue, twos[i]); } - while (queue_has_nonstale(&queue)) { - struct commit *commit = prio_queue_get_dedup(&queue); + while (queue.max_nonstale) { + struct commit *commit = nonstale_queue_get_dedup(&queue); struct commit_list *parents; int flags; timestamp_t generation = commit_graph_generation(commit); @@ -125,7 +157,7 @@ static int paint_down_to_common(struct repository *r, if ((p->object.flags & flags) == flags) continue; if (repo_parse_commit(r, p)) { - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); commit_list_free(*result); *result = NULL; /* @@ -141,11 +173,11 @@ static int paint_down_to_common(struct repository *r, oid_to_hex(&p->object.oid)); } p->object.flags |= flags; - prio_queue_put_dedup(&queue, p); + nonstale_queue_put_dedup(&queue, p); } } - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); commit_list_sort_by_date(result); return 0; } @@ -1039,11 +1071,11 @@ struct commit_list *get_reachable_subset(struct commit **from, size_t nr_from, define_commit_slab(bit_arrays, struct bitmap *); static struct bit_arrays bit_arrays; -static void insert_no_dup(struct prio_queue *queue, struct commit *c) +static void insert_no_dup(struct nonstale_queue *queue, struct commit *c) { if (c->object.flags & PARENT2) return; - prio_queue_put(queue, c); + nonstale_queue_put(queue, c); c->object.flags |= PARENT2; } @@ -1068,7 +1100,9 @@ void ahead_behind(struct repository *r, struct commit **commits, size_t commits_nr, struct ahead_behind_count *counts, size_t counts_nr) { - struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date }; + struct nonstale_queue queue = { + { .compare = compare_commits_by_gen_then_commit_date } + }; size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD); if (!commits_nr || !counts_nr) @@ -1091,8 +1125,8 @@ void ahead_behind(struct repository *r, insert_no_dup(&queue, c); } - while (queue_has_nonstale(&queue)) { - struct commit *c = prio_queue_get(&queue); + while (queue.max_nonstale) { + struct commit *c = nonstale_queue_get(&queue); struct commit_list *p; struct bitmap *bitmap_c = get_bit_array(c, width); @@ -1134,10 +1168,10 @@ void ahead_behind(struct repository *r, /* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks(r, PARENT2 | STALE); - for (size_t i = 0; i < queue.nr; i++) - free_bit_array(queue.array[i].data); + for (size_t i = 0; i < queue.pq.nr; i++) + free_bit_array(queue.pq.array[i].data); clear_bit_arrays(&bit_arrays); - clear_prio_queue(&queue); + clear_nonstale_queue(&queue); } struct commit_and_index {