prio-queue: add prio_queue_replace()

Add a function to replace the top element of the queue that basically does the same as prio_queue_get() followed by prio_queue_put(), but without the work by prio_queue_get() to rebalance the heap. It can be used to optimize loops that get one element and then immediately add another one. That's common e.g., with commit history traversal, where we get out a commit and then put in its parents. Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Jul 18, 2025 at 11:39 UTC 3d5091d232ea991a6a991c86e9fb000f5a9009a0
3 files changed +63 -13
prio-queue.c
+32 -13
@@ -58,22 +58,10 @@ void prio_queue_put(struct prio_queue *queue, void *thing)
58 }
59 }
60
61 -void *prio_queue_get(struct prio_queue *queue)
61 +static void sift_down_root(struct prio_queue *queue)
62 {
63 - void *result;
63 size_t ix, child;
64
66 - if (!queue->nr)
67 - return NULL;
68 - if (!queue->compare)
69 - return queue->array[--queue->nr].data; /* LIFO */
70 -
71 - result = queue->array[0].data;
72 - if (!--queue->nr)
73 - return result;
74 -
75 - queue->array[0] = queue->array[queue->nr];
76 -
65 /* Push down the one at the root */
66 for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
67 child = ix * 2 + 1; /* left */
@@ -86,6 +74,23 @@ void *prio_queue_get(struct prio_queue *queue)
74
75 swap(queue, child, ix);
76 }
77 +}
78 +
79 +void *prio_queue_get(struct prio_queue *queue)
80 +{
81 + void *result;
82 +
83 + if (!queue->nr)
84 + return NULL;
85 + if (!queue->compare)
86 + return queue->array[--queue->nr].data; /* LIFO */
87 +
88 + result = queue->array[0].data;
89 + if (!--queue->nr)
90 + return result;
91 +
92 + queue->array[0] = queue->array[queue->nr];
93 + sift_down_root(queue);
94 return result;
95 }
96
@@ -97,3 +102,17 @@ void *prio_queue_peek(struct prio_queue *queue)
102 return queue->array[queue->nr - 1].data;
103 return queue->array[0].data;
104 }
105 +
106 +void prio_queue_replace(struct prio_queue *queue, void *thing)
107 +{
108 + if (!queue->nr) {
109 + prio_queue_put(queue, thing);
110 + } else if (!queue->compare) {
111 + queue->array[queue->nr - 1].ctr = queue->insertion_ctr++;
112 + queue->array[queue->nr - 1].data = thing;
113 + } else {
114 + queue->array[0].ctr = queue->insertion_ctr++;
115 + queue->array[0].data = thing;
116 + sift_down_root(queue);
117 + }
118 +}
prio-queue.h
+8
@@ -52,6 +52,14 @@ void *prio_queue_get(struct prio_queue *);
52 */
53 void *prio_queue_peek(struct prio_queue *);
54
55 +/*
56 + * Replace the "thing" that compares the smallest with a new "thing",
57 + * like prio_queue_get()+prio_queue_put() would do, but in a more
58 + * efficient way. Does the same as prio_queue_put() if the queue is
59 + * empty.
60 + */
61 +void prio_queue_replace(struct prio_queue *queue, void *thing);
62 +
63 void clear_prio_queue(struct prio_queue *);
64
65 /* Reverse the LIFO elements */
t/unit-tests/u-prio-queue.c
+23
@@ -13,6 +13,7 @@ static int intcmp(const void *va, const void *vb, void *data UNUSED)
13 #define STACK -3
14 #define GET -4
15 #define REVERSE -5
16 +#define REPLACE -6
17
18 static int show(int *v)
19 {
@@ -51,6 +52,15 @@ static void test_prio_queue(int *input, size_t input_size,
52 case REVERSE:
53 prio_queue_reverse(&pq);
54 break;
55 + case REPLACE:
56 + peek = prio_queue_peek(&pq);
57 + cl_assert(i + 1 < input_size);
58 + cl_assert(input[i + 1] >= 0);
59 + cl_assert(j < result_size);
60 + cl_assert_equal_i(result[j], show(peek));
61 + j++;
62 + prio_queue_replace(&pq, &input[++i]);
63 + break;
64 default:
65 prio_queue_put(&pq, &input[i]);
66 break;
@@ -81,6 +91,13 @@ void test_prio_queue__empty(void)
91 ((int []){ 1, 2, MISSING, 1, 2, MISSING }));
92 }
93
94 +void test_prio_queue__replace(void)
95 +{
96 + TEST_INPUT(((int []){ REPLACE, 6, 2, 4, REPLACE, 5, 7, GET,
97 + REPLACE, 1, DUMP }),
98 + ((int []){ MISSING, 2, 4, 5, 1, 6, 7 }));
99 +}
100 +
101 void test_prio_queue__stack(void)
102 {
103 TEST_INPUT(((int []){ STACK, 8, 1, 5, 4, 6, 2, 3, DUMP }),
@@ -92,3 +109,9 @@ void test_prio_queue__reverse_stack(void)
109 TEST_INPUT(((int []){ STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP }),
110 ((int []){ 1, 2, 3, 4, 5, 6 }));
111 }
112 +
113 +void test_prio_queue__replace_stack(void)
114 +{
115 + TEST_INPUT(((int []){ STACK, 8, 1, 5, REPLACE, 4, 6, 2, 3, DUMP }),
116 + ((int []){ 5, 3, 2, 6, 4, 1, 8 }));
117 +}