mirror of
https://github.com/git-for-windows/git.git
synced 2026-06-14 15:01:15 -05:00
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>
127 lines
2.7 KiB
C
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;
|
|
}
|