Skip to content

Commit a155ab2

Browse files
pks-tgitster
authored andcommitted
reftable/block: reuse zstream when writing log blocks
While most reftable blocks are written to disk as-is, blocks for log records are compressed with zlib. To compress them we use `compress2()`, which is a simple wrapper around the more complex `zstream` interface that would require multiple function invocations. One downside of this interface is that `compress2()` will reallocate internal state of the `zstream` interface on every single invocation. Consequently, as we call `compress2()` for every single log block which we are about to write, this can lead to quite some memory allocation churn. Refactor the code so that the block writer reuses a `zstream`. This significantly reduces the number of bytes allocated when writing many refs in a single transaction, as demonstrated by the following benchmark that writes 100k refs in a single transaction. Before: HEAP SUMMARY: in use at exit: 671,931 bytes in 151 blocks total heap usage: 22,631,887 allocs, 22,631,736 frees, 1,854,670,793 bytes allocated After: HEAP SUMMARY: in use at exit: 671,931 bytes in 151 blocks total heap usage: 22,620,528 allocs, 22,620,377 frees, 1,245,549,984 bytes allocated Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8aaeffe commit a155ab2

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

reftable/block.c

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf,
7676
bw->entries = 0;
7777
bw->restart_len = 0;
7878
bw->last_key.len = 0;
79+
if (!bw->zstream) {
80+
REFTABLE_CALLOC_ARRAY(bw->zstream, 1);
81+
deflateInit(bw->zstream, 9);
82+
}
7983
}
8084

8185
uint8_t block_writer_type(struct block_writer *bw)
@@ -139,39 +143,57 @@ int block_writer_finish(struct block_writer *w)
139143
w->next += 2;
140144
put_be24(w->buf + 1 + w->header_off, w->next);
141145

146+
/*
147+
* Log records are stored zlib-compressed. Note that the compression
148+
* also spans over the restart points we have just written.
149+
*/
142150
if (block_writer_type(w) == BLOCK_TYPE_LOG) {
143151
int block_header_skip = 4 + w->header_off;
144-
uLongf src_len = w->next - block_header_skip;
145-
uLongf dest_cap = src_len * 1.001 + 12;
146-
uint8_t *compressed;
147-
148-
REFTABLE_ALLOC_ARRAY(compressed, dest_cap);
149-
150-
while (1) {
151-
uLongf out_dest_len = dest_cap;
152-
int zresult = compress2(compressed, &out_dest_len,
153-
w->buf + block_header_skip,
154-
src_len, 9);
155-
if (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) {
156-
dest_cap *= 2;
157-
compressed =
158-
reftable_realloc(compressed, dest_cap);
159-
if (compressed)
160-
continue;
161-
}
162-
163-
if (Z_OK != zresult) {
164-
reftable_free(compressed);
165-
return REFTABLE_ZLIB_ERROR;
166-
}
167-
168-
memcpy(w->buf + block_header_skip, compressed,
169-
out_dest_len);
170-
w->next = out_dest_len + block_header_skip;
152+
uLongf src_len = w->next - block_header_skip, compressed_len;
153+
unsigned char *compressed;
154+
int ret;
155+
156+
ret = deflateReset(w->zstream);
157+
if (ret != Z_OK)
158+
return REFTABLE_ZLIB_ERROR;
159+
160+
/*
161+
* Precompute the upper bound of how many bytes the compressed
162+
* data may end up with. Combined with `Z_FINISH`, `deflate()`
163+
* is guaranteed to return `Z_STREAM_END`.
164+
*/
165+
compressed_len = deflateBound(w->zstream, src_len);
166+
REFTABLE_ALLOC_ARRAY(compressed, compressed_len);
167+
168+
w->zstream->next_out = compressed;
169+
w->zstream->avail_out = compressed_len;
170+
w->zstream->next_in = w->buf + block_header_skip;
171+
w->zstream->avail_in = src_len;
172+
173+
/*
174+
* We want to perform all decompression in a single step, which
175+
* is why we can pass Z_FINISH here. As we have precomputed the
176+
* deflated buffer's size via `deflateBound()` this function is
177+
* guaranteed to succeed according to the zlib documentation.
178+
*/
179+
ret = deflate(w->zstream, Z_FINISH);
180+
if (ret != Z_STREAM_END) {
171181
reftable_free(compressed);
172-
break;
182+
return REFTABLE_ZLIB_ERROR;
173183
}
184+
185+
/*
186+
* Overwrite the uncompressed data we have already written and
187+
* adjust the `next` pointer to point right after the
188+
* compressed data.
189+
*/
190+
memcpy(w->buf + block_header_skip, compressed,
191+
w->zstream->total_out);
192+
w->next = w->zstream->total_out + block_header_skip;
193+
194+
reftable_free(compressed);
174195
}
196+
175197
return w->next;
176198
}
177199

@@ -425,6 +447,8 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
425447

426448
void block_writer_release(struct block_writer *bw)
427449
{
450+
deflateEnd(bw->zstream);
451+
FREE_AND_NULL(bw->zstream);
428452
FREE_AND_NULL(bw->restarts);
429453
strbuf_release(&bw->last_key);
430454
/* the block is not owned. */

reftable/block.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ license that can be found in the LICENSE file or at
1818
* allocation overhead.
1919
*/
2020
struct block_writer {
21+
z_stream *zstream;
2122
uint8_t *buf;
2223
uint32_t block_size;
2324

0 commit comments

Comments
 (0)