For each subiterator, the merged table needs to track their current
record. This record is owned by the priority queue though instead of by
the merged iterator. This is not optimal performance-wise.
For one, we need to move around records whenever we add or remove a
record from the priority queue. Thus, the bigger the entries the more
bytes we need to copy around. And compared to pointers, a reftable
record is rather on the bigger side. The other issue is that this makes
it harder to reuse the records.
Refactor the code so that the merged iterator tracks ownership of the
records per-subiter. Instead of having records in the priority queue, we
can now use mere pointers to the per-subiter records. This also allows
us to swap records between the caller and the per-subiter record instead
of doing an actual copy via `reftable_record_copy_from()`, which removes
the need to release the caller-provided record.
This results in a noticeable speedup when iterating through many refs.
The following benchmark iterates through 1 million refs:
Benchmark 1: show-ref: single matching ref (revision = HEAD~)
Time (mean ± σ): 145.5 ms ± 4.5 ms [User: 142.5 ms, System: 2.8 ms]
Range (min … max): 141.3 ms … 177.0 ms 1000 runs
Benchmark 2: show-ref: single matching ref (revision = HEAD)
Time (mean ± σ): 139.0 ms ± 4.7 ms [User: 136.1 ms, System: 2.8 ms]
Range (min … max): 134.2 ms … 182.2 ms 1000 runs
Summary
show-ref: single matching ref (revision = HEAD) ran
1.05 ± 0.05 times faster than show-ref: single matching ref (revision = HEAD~)
This refactoring also allows a subsequent refactoring where we start
reusing memory allocated by the reftable records because we do not need
to release the caller-provided record anymore.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
88 lines
1.6 KiB
C
88 lines
1.6 KiB
C
/*
|
|
Copyright 2020 Google LLC
|
|
|
|
Use of this source code is governed by a BSD-style
|
|
license that can be found in the LICENSE file or at
|
|
https://developers.google.com/open-source/licenses/bsd
|
|
*/
|
|
|
|
#include "pq.h"
|
|
|
|
#include "reftable-record.h"
|
|
#include "system.h"
|
|
#include "basics.h"
|
|
|
|
int pq_less(struct pq_entry *a, struct pq_entry *b)
|
|
{
|
|
int cmp = reftable_record_cmp(a->rec, b->rec);
|
|
if (cmp == 0)
|
|
return a->index > b->index;
|
|
return cmp < 0;
|
|
}
|
|
|
|
struct pq_entry merged_iter_pqueue_top(struct merged_iter_pqueue pq)
|
|
{
|
|
return pq.heap[0];
|
|
}
|
|
|
|
int merged_iter_pqueue_is_empty(struct merged_iter_pqueue pq)
|
|
{
|
|
return pq.len == 0;
|
|
}
|
|
|
|
struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
|
|
{
|
|
int i = 0;
|
|
struct pq_entry e = pq->heap[0];
|
|
pq->heap[0] = pq->heap[pq->len - 1];
|
|
pq->len--;
|
|
|
|
i = 0;
|
|
while (i < pq->len) {
|
|
int min = i;
|
|
int j = 2 * i + 1;
|
|
int k = 2 * i + 2;
|
|
if (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) {
|
|
min = j;
|
|
}
|
|
if (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) {
|
|
min = k;
|
|
}
|
|
|
|
if (min == i) {
|
|
break;
|
|
}
|
|
|
|
SWAP(pq->heap[i], pq->heap[min]);
|
|
i = min;
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry *e)
|
|
{
|
|
int i = 0;
|
|
|
|
REFTABLE_ALLOC_GROW(pq->heap, pq->len + 1, pq->cap);
|
|
pq->heap[pq->len++] = *e;
|
|
|
|
i = pq->len - 1;
|
|
while (i > 0) {
|
|
int j = (i - 1) / 2;
|
|
if (pq_less(&pq->heap[j], &pq->heap[i])) {
|
|
break;
|
|
}
|
|
|
|
SWAP(pq->heap[j], pq->heap[i]);
|
|
|
|
i = j;
|
|
}
|
|
}
|
|
|
|
void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
|
|
{
|
|
FREE_AND_NULL(pq->heap);
|
|
memset(pq, 0, sizeof(*pq));
|
|
}
|