Files
git/prio-queue.c
Kristofer Karlsson 9f75e7a150 prio-queue: fold lazy_queue into prio_queue for automatic get+put fusion
Defer the actual removal in prio_queue_get() until the next
operation.  If that next operation is a prio_queue_put(), the
removal and insertion are fused into a single replace — writing
the new element at the root and sifting it down — which avoids
a full remove-rebalance-insert cycle.

This matches the dominant usage pattern in git's commit traversal:
get a commit, then put its parents.  The first parent insertion
after each get is now a replace operation automatically.

This generalizes the lazy_queue pattern from builtin/describe.c
(introduced in 08bb69d70f) into prio_queue itself.  Three callers
independently implemented the same get+put fusion:

  - builtin/describe.c had a full lazy_queue wrapper
  - commit.c:pop_most_recent_commit() used peek+replace
  - builtin/show-branch.c:join_revs() used peek+replace

All three now collapse to plain _get() and _put(), with the data
structure handling the fusion internally.  This simplifies callers
and means every prio_queue user gets the optimization for free
without needing to implement it manually.

Remove prio_queue_replace() since no external callers remain.

Benchmarked on a 1.8M-commit monorepo (30 interleaved runs,
paired t-test, Xeon @ 2.20GHz):

Code paths that previously did eager get+put (new optimization):

  Command                       base    patched  change      p
  merge-base --all A A~1000     3828ms  3725ms   -2.69%  0.0001
  rev-list --count A~1000..A    3055ms  2986ms   -2.27%  0.0601
  log --oneline A~1000..A       3408ms  3350ms   -1.71%  0.0482

Code paths that already had manual get+put fusion (expect
neutral — the optimization moves into prio_queue but the number
of heap operations stays the same):

  Command                       base    patched  change      p
  show-branch A A~1000          9156ms  9127ms   -0.32%  0.3470
  describe (4751 revs, 81K repo) 1983ms 1963ms  -1.02%  <0.001

No regressions in any scenario.

Suggested-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Kristofer Karlsson <krka@spotify.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-06-09 11:11:46 -07:00

127 lines
2.7 KiB
C

#include "git-compat-util.h"
#include "prio-queue.h"
static inline int compare(struct prio_queue *queue, size_t i, size_t j)
{
int cmp = queue->compare(queue->array[i].data, queue->array[j].data,
queue->cb_data);
if (!cmp)
cmp = (queue->array[i].ctr > queue->array[j].ctr) -
(queue->array[i].ctr < queue->array[j].ctr);
return cmp;
}
static inline void swap(struct prio_queue *queue, size_t i, size_t j)
{
SWAP(queue->array[i], queue->array[j]);
}
void prio_queue_reverse(struct prio_queue *queue)
{
size_t i, j;
if (queue->compare)
BUG("prio_queue_reverse() on non-LIFO queue");
if (!queue->nr_)
return;
for (i = 0; i < (j = (queue->nr_ - 1) - i); i++)
swap(queue, i, j);
}
void clear_prio_queue(struct prio_queue *queue)
{
FREE_AND_NULL(queue->array);
queue->nr_ = 0;
queue->alloc = 0;
queue->insertion_ctr = 0;
queue->get_pending = 0;
}
static void sift_down_root(struct prio_queue *queue)
{
size_t ix, child;
/* Push down the one at the root */
for (ix = 0; ix * 2 + 1 < queue->nr_; ix = child) {
child = ix * 2 + 1; /* left */
if (child + 1 < queue->nr_ &&
compare(queue, child, child + 1) >= 0)
child++; /* use right child */
if (compare(queue, ix, child) <= 0)
break;
swap(queue, child, ix);
}
}
static inline void flush_get(struct prio_queue *queue)
{
if (!queue->get_pending)
return;
queue->get_pending = 0;
queue->array[0] = queue->array[--queue->nr_];
sift_down_root(queue);
}
void prio_queue_put(struct prio_queue *queue, void *thing)
{
size_t ix, parent;
if (queue->get_pending) {
queue->get_pending = 0;
queue->array[0].ctr = queue->insertion_ctr++;
queue->array[0].data = thing;
sift_down_root(queue);
return;
}
/* Append at the end */
ALLOC_GROW(queue->array, queue->nr_ + 1, queue->alloc);
queue->array[queue->nr_].ctr = queue->insertion_ctr++;
queue->array[queue->nr_].data = thing;
queue->nr_++;
if (!queue->compare)
return; /* LIFO */
/* Bubble up the new one */
for (ix = queue->nr_ - 1; ix; ix = parent) {
parent = (ix - 1) / 2;
if (compare(queue, parent, ix) <= 0)
break;
swap(queue, parent, ix);
}
}
void *prio_queue_get(struct prio_queue *queue)
{
if (queue->nr_ <= queue->get_pending) {
queue->nr_ = 0;
queue->get_pending = 0;
return NULL;
}
if (!queue->compare)
return queue->array[--queue->nr_].data; /* LIFO */
flush_get(queue);
queue->get_pending = 1;
return queue->array[0].data;
}
void *prio_queue_peek(struct prio_queue *queue)
{
if (queue->nr_ <= queue->get_pending) {
queue->nr_ = 0;
queue->get_pending = 0;
return NULL;
}
if (!queue->compare)
return queue->array[queue->nr_ - 1].data;
flush_get(queue);
return queue->array[0].data;
}