Merge branch 'kk/commit-reach-optim'

The check for non-stale commits in the priority queue used by
`paint_down_to_common` and `ahead_behind` has been optimized by
replacing an O(N) scan with an O(1) counter, yielding performance
improvements in repositories with wide histories.

* kk/commit-reach-optim:
  commit-reach: replace queue_has_nonstale() scan with O(1) tracking
  commit-reach: deduplicate queue entries in paint_down_to_common
  object.h: fix stale entries in object flag allocation table
main
Junio C Hamano 2026-06-07 23:58:25 +09:00
commit 6390da42c7
2 changed files with 80 additions and 28 deletions

View File

@ -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,14 +40,60 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
return 0;
}

static int queue_has_nonstale(struct prio_queue *queue)
/*
* 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)
{
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;
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;
nonstale_queue_put(queue, c);
}

static struct commit *nonstale_queue_get_dedup(struct nonstale_queue *queue)
{
struct commit *commit = nonstale_queue_get(queue);

if (commit)
commit->object.flags &= ~ENQUEUED;
return commit;
}

/* all input commits in one and twos[] must have been parsed! */
@ -57,28 +104,30 @@ static int paint_down_to_common(struct repository *r,
enum merge_base_flags mb_flags,
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(&queue, one);
nonstale_queue_put_dedup(&queue, one);

for (i = 0; i < n; i++) {
twos[i]->object.flags |= PARENT2;
prio_queue_put(&queue, twos[i]);
nonstale_queue_put_dedup(&queue, twos[i]);
}

while (queue_has_nonstale(&queue)) {
struct commit *commit = prio_queue_get(&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);
@ -116,7 +165,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;
/*
@ -132,11 +181,11 @@ static int paint_down_to_common(struct repository *r,
oid_to_hex(&p->object.oid));
}
p->object.flags |= flags;
prio_queue_put(&queue, p);
nonstale_queue_put_dedup(&queue, p);
}
}

clear_prio_queue(&queue);
clear_nonstale_queue(&queue);
commit_list_sort_by_date(result);
return 0;
}
@ -1040,11 +1089,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;
}

@ -1069,7 +1118,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)
@ -1092,8 +1143,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);

@ -1135,10 +1186,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 {

View File

@ -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
@ -74,15 +75,15 @@ 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
* 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
*/