describe: use prio_queue_replace()

Optimize the sequence get+put to peek+replace to avoid one unnecessary
heap rebalance.

Do that by tracking partial get operations in a prio_queue wrapper,
struct lazy_queue, and using wrapper functions that turn get into peek
and put into replace as needed.  This is simpler than tracking the
state explicitly in the calling code.

We get a nice speedup on top of the previous patch's conversion to
prio_queue:

Benchmark 1: ./git_2.50.1 describe $(git rev-list v2.41.0..v2.47.0)
  Time (mean ± σ):      1.559 s ±  0.002 s    [User: 1.493 s, System: 0.051 s]
  Range (min … max):    1.556 s …  1.563 s    10 runs

Benchmark 2: ./git_describe_pq describe $(git rev-list v2.41.0..v2.47.0)
  Time (mean ± σ):      1.204 s ±  0.001 s    [User: 1.138 s, System: 0.051 s]
  Range (min … max):    1.202 s …  1.205 s    10 runs

Benchmark 3: ./git describe $(git rev-list v2.41.0..v2.47.0)
  Time (mean ± σ):     850.9 ms ±   1.6 ms    [User: 786.6 ms, System: 49.8 ms]
  Range (min … max):   849.1 ms … 854.1 ms    10 runs

Summary
  ./git describe $(git rev-list v2.41.0..v2.47.0) ran
    1.41 ± 0.00 times faster than ./git_describe_pq describe $(git rev-list v2.41.0..v2.47.0)
    1.83 ± 0.00 times faster than ./git_2.50.1 describe $(git rev-list v2.41.0..v2.47.0)

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
main
René Scharfe 2025-08-03 13:49:11 +02:00 committed by Junio C Hamano
parent 66e2adb8f6
commit 08bb69d70f
1 changed files with 52 additions and 16 deletions

View File

@ -250,22 +250,58 @@ static int compare_pt(const void *a_, const void *b_)
return 0; return 0;
} }


static bool all_have_flag(const struct prio_queue *queue, unsigned flag) struct lazy_queue {
struct prio_queue queue;
bool get_pending;
};

#define LAZY_QUEUE_INIT { { compare_commits_by_commit_date }, false }

static void *lazy_queue_get(struct lazy_queue *queue)
{ {
for (size_t i = 0; i < queue->nr; i++) { if (queue->get_pending)
struct commit *commit = queue->array[i].data; prio_queue_get(&queue->queue);
else
queue->get_pending = true;
return prio_queue_peek(&queue->queue);
}

static void lazy_queue_put(struct lazy_queue *queue, void *thing)
{
if (queue->get_pending)
prio_queue_replace(&queue->queue, thing);
else
prio_queue_put(&queue->queue, thing);
queue->get_pending = false;
}

static bool lazy_queue_empty(const struct lazy_queue *queue)
{
return queue->queue.nr == (queue->get_pending ? 1 : 0);
}

static void lazy_queue_clear(struct lazy_queue *queue)
{
clear_prio_queue(&queue->queue);
queue->get_pending = false;
}

static bool all_have_flag(const struct lazy_queue *queue, unsigned flag)
{
for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) {
struct commit *commit = queue->queue.array[i].data;
if (!(commit->object.flags & flag)) if (!(commit->object.flags & flag))
return false; return false;
} }
return true; return true;
} }


static unsigned long finish_depth_computation(struct prio_queue *queue, static unsigned long finish_depth_computation(struct lazy_queue *queue,
struct possible_tag *best) struct possible_tag *best)
{ {
unsigned long seen_commits = 0; unsigned long seen_commits = 0;
while (queue->nr) { while (!lazy_queue_empty(queue)) {
struct commit *c = prio_queue_get(queue); struct commit *c = lazy_queue_get(queue);
struct commit_list *parents = c->parents; struct commit_list *parents = c->parents;
seen_commits++; seen_commits++;
if (c->object.flags & best->flag_within) { if (c->object.flags & best->flag_within) {
@ -277,7 +313,7 @@ static unsigned long finish_depth_computation(struct prio_queue *queue,
struct commit *p = parents->item; struct commit *p = parents->item;
repo_parse_commit(the_repository, p); repo_parse_commit(the_repository, p);
if (!(p->object.flags & SEEN)) if (!(p->object.flags & SEEN))
prio_queue_put(queue, p); lazy_queue_put(queue, p);
p->object.flags |= c->object.flags; p->object.flags |= c->object.flags;
parents = parents->next; parents = parents->next;
} }
@ -319,7 +355,7 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf
static void describe_commit(struct object_id *oid, struct strbuf *dst) static void describe_commit(struct object_id *oid, struct strbuf *dst)
{ {
struct commit *cmit, *gave_up_on = NULL; struct commit *cmit, *gave_up_on = NULL;
struct prio_queue queue = { compare_commits_by_commit_date }; struct lazy_queue queue = LAZY_QUEUE_INIT;
struct commit_name *n; struct commit_name *n;
struct possible_tag all_matches[MAX_TAGS]; struct possible_tag all_matches[MAX_TAGS];
unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
@ -363,9 +399,9 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
} }


cmit->object.flags = SEEN; cmit->object.flags = SEEN;
prio_queue_put(&queue, cmit); lazy_queue_put(&queue, cmit);
while (queue.nr) { while (!lazy_queue_empty(&queue)) {
struct commit *c = prio_queue_get(&queue); struct commit *c = lazy_queue_get(&queue);
struct commit_list *parents = c->parents; struct commit_list *parents = c->parents;
struct commit_name **slot; struct commit_name **slot;


@ -399,7 +435,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
t->depth++; t->depth++;
} }
/* Stop if last remaining path already covered by best candidate(s) */ /* Stop if last remaining path already covered by best candidate(s) */
if (annotated_cnt && !queue.nr) { if (annotated_cnt && lazy_queue_empty(&queue)) {
int best_depth = INT_MAX; int best_depth = INT_MAX;
unsigned best_within = 0; unsigned best_within = 0;
for (cur_match = 0; cur_match < match_cnt; cur_match++) { for (cur_match = 0; cur_match < match_cnt; cur_match++) {
@ -422,7 +458,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
struct commit *p = parents->item; struct commit *p = parents->item;
repo_parse_commit(the_repository, p); repo_parse_commit(the_repository, p);
if (!(p->object.flags & SEEN)) if (!(p->object.flags & SEEN))
prio_queue_put(&queue, p); lazy_queue_put(&queue, p);
p->object.flags |= c->object.flags; p->object.flags |= c->object.flags;
parents = parents->next; parents = parents->next;


@ -437,7 +473,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
strbuf_add_unique_abbrev(dst, cmit_oid, abbrev); strbuf_add_unique_abbrev(dst, cmit_oid, abbrev);
if (suffix) if (suffix)
strbuf_addstr(dst, suffix); strbuf_addstr(dst, suffix);
clear_prio_queue(&queue); lazy_queue_clear(&queue);
return; return;
} }
if (unannotated_cnt) if (unannotated_cnt)
@ -453,11 +489,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
QSORT(all_matches, match_cnt, compare_pt); QSORT(all_matches, match_cnt, compare_pt);


if (gave_up_on) { if (gave_up_on) {
prio_queue_put(&queue, gave_up_on); lazy_queue_put(&queue, gave_up_on);
seen_commits--; seen_commits--;
} }
seen_commits += finish_depth_computation(&queue, &all_matches[0]); seen_commits += finish_depth_computation(&queue, &all_matches[0]);
clear_prio_queue(&queue); lazy_queue_clear(&queue);


if (debug) { if (debug) {
static int label_width = -1; static int label_width = -1;