describe: use prio_queue

Replace the use a list-based priority queue whose order is maintained by commit_list_insert_by_date() with a prio_queue. This avoids quadratic worst-case complexity. And in the somewhat contrived example of describing the 4751 commits from v2.41.0 to v2.47.0 in one go (to get a sizable chunk of describe work with minimal ref loading overhead) it's significantly faster: Benchmark 1: ./git_2.50.1 describe $(git rev-list v2.41.0..v2.47.0) Time (mean ± σ): 1.558 s ± 0.002 s [User: 1.492 s, System: 0.051 s] Range (min … max): 1.557 s … 1.562 s 10 runs Benchmark 2: ./git describe $(git rev-list v2.41.0..v2.47.0) Time (mean ± σ): 1.209 s ± 0.006 s [User: 1.143 s, System: 0.051 s] Range (min … max): 1.201 s … 1.219 s 10 runs Summary ./git describe $(git rev-list v2.41.0..v2.47.0) ran 1.29 ± 0.01 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>

René Scharfe committed Aug 3, 2025 at 13:38 UTC 66e2adb8f6fe97bb480d96205fb3473b8c1fe4df
1 file changed +27 -24
builtin/describe.c
+27 -24
@@ -23,6 +23,7 @@
23 #include "list-objects.h"
24 #include "commit-slab.h"
25 #include "wildmatch.h"
26 +#include "prio-queue.h"
27
28 #define MAX_TAGS (FLAG_BITS - 1)
29 #define DEFAULT_CANDIDATES 10
@@ -249,24 +250,26 @@ static int compare_pt(const void *a_, const void *b_)
250 return 0;
251 }
252
252 -static unsigned long finish_depth_computation(
253 - struct commit_list **list,
254 - struct possible_tag *best)
253 +static bool all_have_flag(const struct prio_queue *queue, unsigned flag)
254 +{
255 + for (size_t i = 0; i < queue->nr; i++) {
256 + struct commit *commit = queue->array[i].data;
257 + if (!(commit->object.flags & flag))
258 + return false;
259 + }
260 + return true;
261 +}
262 +
263 +static unsigned long finish_depth_computation(struct prio_queue *queue,
264 + struct possible_tag *best)
265 {
266 unsigned long seen_commits = 0;
257 - while (*list) {
258 - struct commit *c = pop_commit(list);
267 + while (queue->nr) {
268 + struct commit *c = prio_queue_get(queue);
269 struct commit_list *parents = c->parents;
270 seen_commits++;
271 if (c->object.flags & best->flag_within) {
262 - struct commit_list *a = *list;
263 - while (a) {
264 - struct commit *i = a->item;
265 - if (!(i->object.flags & best->flag_within))
266 - break;
267 - a = a->next;
268 - }
269 - if (!a)
272 + if (all_have_flag(queue, best->flag_within))
273 break;
274 } else
275 best->depth++;
@@ -274,7 +277,7 @@ static unsigned long finish_depth_computation(
277 struct commit *p = parents->item;
278 repo_parse_commit(the_repository, p);
279 if (!(p->object.flags & SEEN))
277 - commit_list_insert_by_date(p, list);
280 + prio_queue_put(queue, p);
281 p->object.flags |= c->object.flags;
282 parents = parents->next;
283 }
@@ -316,7 +319,7 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf
319 static void describe_commit(struct object_id *oid, struct strbuf *dst)
320 {
321 struct commit *cmit, *gave_up_on = NULL;
319 - struct commit_list *list;
322 + struct prio_queue queue = { compare_commits_by_commit_date };
323 struct commit_name *n;
324 struct possible_tag all_matches[MAX_TAGS];
325 unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
@@ -359,11 +362,10 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
362 have_util = 1;
363 }
364
362 - list = NULL;
365 cmit->object.flags = SEEN;
364 - commit_list_insert(cmit, &list);
365 - while (list) {
366 - struct commit *c = pop_commit(&list);
366 + prio_queue_put(&queue, cmit);
367 + while (queue.nr) {
368 + struct commit *c = prio_queue_get(&queue);
369 struct commit_list *parents = c->parents;
370 struct commit_name **slot;
371
@@ -397,7 +399,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
399 t->depth++;
400 }
401 /* Stop if last remaining path already covered by best candidate(s) */
400 - if (annotated_cnt && !list) {
402 + if (annotated_cnt && !queue.nr) {
403 int best_depth = INT_MAX;
404 unsigned best_within = 0;
405 for (cur_match = 0; cur_match < match_cnt; cur_match++) {
@@ -420,7 +422,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
422 struct commit *p = parents->item;
423 repo_parse_commit(the_repository, p);
424 if (!(p->object.flags & SEEN))
423 - commit_list_insert_by_date(p, &list);
425 + prio_queue_put(&queue, p);
426 p->object.flags |= c->object.flags;
427 parents = parents->next;
428
@@ -435,6 +437,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
437 strbuf_add_unique_abbrev(dst, cmit_oid, abbrev);
438 if (suffix)
439 strbuf_addstr(dst, suffix);
440 + clear_prio_queue(&queue);
441 return;
442 }
443 if (unannotated_cnt)
@@ -450,11 +453,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
453 QSORT(all_matches, match_cnt, compare_pt);
454
455 if (gave_up_on) {
453 - commit_list_insert_by_date(gave_up_on, &list);
456 + prio_queue_put(&queue, gave_up_on);
457 seen_commits--;
458 }
456 - seen_commits += finish_depth_computation(&list, &all_matches[0]);
457 - free_commit_list(list);
459 + seen_commits += finish_depth_computation(&queue, &all_matches[0]);
460 + clear_prio_queue(&queue);
461
462 if (debug) {
463 static int label_width = -1;