Skip to content

Commit bb2d6be

Browse files
pks-tgitster
authored andcommitted
reftable/merged: make subiters own their records
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent aad8ad6 commit bb2d6be

File tree

4 files changed

+49
-56
lines changed

4 files changed

+49
-56
lines changed

reftable/merged.c

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ license that can be found in the LICENSE file or at
1717
#include "reftable-error.h"
1818
#include "system.h"
1919

20+
struct merged_subiter {
21+
struct reftable_iterator iter;
22+
struct reftable_record rec;
23+
};
24+
2025
struct merged_iter {
21-
struct reftable_iterator *stack;
26+
struct merged_subiter *subiters;
2227
struct merged_iter_pqueue pq;
2328
uint32_t hash_id;
2429
size_t stack_len;
@@ -32,16 +37,18 @@ static int merged_iter_init(struct merged_iter *mi)
3237
for (size_t i = 0; i < mi->stack_len; i++) {
3338
struct pq_entry e = {
3439
.index = i,
40+
.rec = &mi->subiters[i].rec,
3541
};
3642
int err;
3743

38-
reftable_record_init(&e.rec, mi->typ);
39-
err = iterator_next(&mi->stack[i], &e.rec);
44+
reftable_record_init(&mi->subiters[i].rec, mi->typ);
45+
err = iterator_next(&mi->subiters[i].iter,
46+
&mi->subiters[i].rec);
4047
if (err < 0)
4148
return err;
4249
if (err > 0) {
43-
reftable_iterator_destroy(&mi->stack[i]);
44-
reftable_record_release(&e.rec);
50+
reftable_iterator_destroy(&mi->subiters[i].iter);
51+
reftable_record_release(&mi->subiters[i].rec);
4552
continue;
4653
}
4754

@@ -56,27 +63,28 @@ static void merged_iter_close(void *p)
5663
struct merged_iter *mi = p;
5764

5865
merged_iter_pqueue_release(&mi->pq);
59-
for (size_t i = 0; i < mi->stack_len; i++)
60-
reftable_iterator_destroy(&mi->stack[i]);
61-
reftable_free(mi->stack);
66+
for (size_t i = 0; i < mi->stack_len; i++) {
67+
reftable_iterator_destroy(&mi->subiters[i].iter);
68+
reftable_record_release(&mi->subiters[i].rec);
69+
}
70+
reftable_free(mi->subiters);
6271
}
6372

6473
static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
6574
size_t idx)
6675
{
6776
struct pq_entry e = {
6877
.index = idx,
78+
.rec = &mi->subiters[idx].rec,
6979
};
7080
int err;
7181

72-
reftable_record_init(&e.rec, mi->typ);
73-
err = iterator_next(&mi->stack[idx], &e.rec);
82+
err = iterator_next(&mi->subiters[idx].iter, &mi->subiters[idx].rec);
7483
if (err < 0)
7584
return err;
76-
7785
if (err > 0) {
78-
reftable_iterator_destroy(&mi->stack[idx]);
79-
reftable_record_release(&e.rec);
86+
reftable_iterator_destroy(&mi->subiters[idx].iter);
87+
reftable_record_release(&mi->subiters[idx].rec);
8088
return 0;
8189
}
8290

@@ -86,7 +94,7 @@ static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
8694

8795
static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)
8896
{
89-
if (iterator_is_null(&mi->stack[idx]))
97+
if (iterator_is_null(&mi->subiters[idx].iter))
9098
return 0;
9199
return merged_iter_advance_nonnull_subiter(mi, idx);
92100
}
@@ -121,25 +129,19 @@ static int merged_iter_next_entry(struct merged_iter *mi,
121129
struct pq_entry top = merged_iter_pqueue_top(mi->pq);
122130
int cmp;
123131

124-
cmp = reftable_record_cmp(&top.rec, &entry.rec);
132+
cmp = reftable_record_cmp(top.rec, entry.rec);
125133
if (cmp > 0)
126134
break;
127135

128136
merged_iter_pqueue_remove(&mi->pq);
129137
err = merged_iter_advance_subiter(mi, top.index);
130138
if (err < 0)
131-
goto done;
132-
reftable_record_release(&top.rec);
139+
return err;
133140
}
134141

135-
reftable_record_release(rec);
136-
*rec = entry.rec;
137142
mi->advance_index = entry.index;
138-
139-
done:
140-
if (err)
141-
reftable_record_release(&entry.rec);
142-
return err;
143+
SWAP(*rec, *entry.rec);
144+
return 0;
143145
}
144146

145147
static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)
@@ -257,10 +259,10 @@ static int merged_table_seek_record(struct reftable_merged_table *mt,
257259
struct merged_iter *p;
258260
int err;
259261

260-
REFTABLE_CALLOC_ARRAY(merged.stack, mt->stack_len);
262+
REFTABLE_CALLOC_ARRAY(merged.subiters, mt->stack_len);
261263
for (size_t i = 0; i < mt->stack_len; i++) {
262264
err = reftable_table_seek_record(&mt->stack[i],
263-
&merged.stack[merged.stack_len], rec);
265+
&merged.subiters[merged.stack_len].iter, rec);
264266
if (err < 0)
265267
goto out;
266268
if (!err)

reftable/pq.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license that can be found in the LICENSE file or at
1414

1515
int pq_less(struct pq_entry *a, struct pq_entry *b)
1616
{
17-
int cmp = reftable_record_cmp(&a->rec, &b->rec);
17+
int cmp = reftable_record_cmp(a->rec, b->rec);
1818
if (cmp == 0)
1919
return a->index > b->index;
2020
return cmp < 0;
@@ -82,10 +82,6 @@ void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry
8282

8383
void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
8484
{
85-
int i = 0;
86-
for (i = 0; i < pq->len; i++) {
87-
reftable_record_release(&pq->heap[i].rec);
88-
}
8985
FREE_AND_NULL(pq->heap);
90-
pq->len = pq->cap = 0;
86+
memset(pq, 0, sizeof(*pq));
9187
}

reftable/pq.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ license that can be found in the LICENSE file or at
1313

1414
struct pq_entry {
1515
size_t index;
16-
struct reftable_record rec;
16+
struct reftable_record *rec;
1717
};
1818

1919
struct merged_iter_pqueue {

reftable/pq_test.c

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,43 @@ void merged_iter_pqueue_check(struct merged_iter_pqueue pq)
2727

2828
static void test_pq(void)
2929
{
30-
char *names[54] = { NULL };
31-
int N = ARRAY_SIZE(names) - 1;
32-
3330
struct merged_iter_pqueue pq = { NULL };
31+
struct reftable_record recs[54];
32+
int N = ARRAY_SIZE(recs) - 1, i;
3433
char *last = NULL;
3534

36-
int i = 0;
3735
for (i = 0; i < N; i++) {
38-
char name[100];
39-
snprintf(name, sizeof(name), "%02d", i);
40-
names[i] = xstrdup(name);
36+
struct strbuf refname = STRBUF_INIT;
37+
strbuf_addf(&refname, "%02d", i);
38+
39+
reftable_record_init(&recs[i], BLOCK_TYPE_REF);
40+
recs[i].u.ref.refname = strbuf_detach(&refname, NULL);
4141
}
4242

4343
i = 1;
4444
do {
45-
struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
46-
.u.ref = {
47-
.refname = names[i],
48-
} } };
45+
struct pq_entry e = {
46+
.rec = &recs[i],
47+
};
48+
4949
merged_iter_pqueue_add(&pq, &e);
5050
merged_iter_pqueue_check(pq);
51+
5152
i = (i * 7) % N;
5253
} while (i != 1);
5354

5455
while (!merged_iter_pqueue_is_empty(pq)) {
5556
struct pq_entry e = merged_iter_pqueue_remove(&pq);
56-
struct reftable_record *rec = &e.rec;
5757
merged_iter_pqueue_check(pq);
5858

59-
EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
60-
if (last) {
61-
EXPECT(strcmp(last, rec->u.ref.refname) < 0);
62-
}
63-
/* this is names[i], so don't dealloc. */
64-
last = rec->u.ref.refname;
65-
rec->u.ref.refname = NULL;
66-
reftable_record_release(rec);
67-
}
68-
for (i = 0; i < N; i++) {
69-
reftable_free(names[i]);
59+
EXPECT(reftable_record_type(e.rec) == BLOCK_TYPE_REF);
60+
if (last)
61+
EXPECT(strcmp(last, e.rec->u.ref.refname) < 0);
62+
last = e.rec->u.ref.refname;
7063
}
7164

65+
for (i = 0; i < N; i++)
66+
reftable_record_release(&recs[i]);
7267
merged_iter_pqueue_release(&pq);
7368
}
7469

0 commit comments

Comments
 (0)