Skip to content

Commit ce1f213

Browse files
pks-tgitster
authored andcommitted
reftable/block: reuse zstream state on inflation
When calling `inflateInit()` and `inflate()`, the zlib library will allocate several data structures for the underlying `zstream` to keep track of various information. Thus, when inflating repeatedly, it is possible to optimize memory allocation patterns by reusing the `zstream` and then calling `inflateReset()` on it to prepare it for the next chunk of data to inflate. This is exactly what the reftable code is doing: when iterating through reflogs we need to potentially inflate many log blocks, but we discard the `zstream` every single time. Instead, as we reuse the `block_reader` for each of the blocks anyway, we can initialize the `zstream` once and then reuse it for subsequent inflations. Refactor the code to do so, which leads to a significant reduction in the number of allocations. The following measurements were done when iterating through 1 million reflog entries. Before: HEAP SUMMARY: in use at exit: 13,473 bytes in 122 blocks total heap usage: 23,028 allocs, 22,906 frees, 162,813,552 bytes allocated After: HEAP SUMMARY: in use at exit: 13,473 bytes in 122 blocks total heap usage: 302 allocs, 180 frees, 88,352 bytes allocated Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 15a60b7 commit ce1f213

File tree

3 files changed

+19
-10
lines changed

3 files changed

+19
-10
lines changed

reftable/block.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
198198
uint32_t block_header_skip = 4 + header_off;
199199
uLong dst_len = sz - block_header_skip;
200200
uLong src_len = block->len - block_header_skip;
201-
z_stream stream = {0};
202201

203202
/* Log blocks specify the *uncompressed* size in their header. */
204203
REFTABLE_ALLOC_GROW(br->uncompressed_data, sz,
@@ -207,16 +206,21 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
207206
/* Copy over the block header verbatim. It's not compressed. */
208207
memcpy(br->uncompressed_data, block->data, block_header_skip);
209208

210-
err = inflateInit(&stream);
209+
if (!br->zstream) {
210+
REFTABLE_CALLOC_ARRAY(br->zstream, 1);
211+
err = inflateInit(br->zstream);
212+
} else {
213+
err = inflateReset(br->zstream);
214+
}
211215
if (err != Z_OK) {
212216
err = REFTABLE_ZLIB_ERROR;
213217
goto done;
214218
}
215219

216-
stream.next_in = block->data + block_header_skip;
217-
stream.avail_in = src_len;
218-
stream.next_out = br->uncompressed_data + block_header_skip;
219-
stream.avail_out = dst_len;
220+
br->zstream->next_in = block->data + block_header_skip;
221+
br->zstream->avail_in = src_len;
222+
br->zstream->next_out = br->uncompressed_data + block_header_skip;
223+
br->zstream->avail_out = dst_len;
220224

221225
/*
222226
* We know both input as well as output size, and we know that
@@ -225,15 +229,14 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
225229
* here to instruct zlib to inflate the data in one go, which
226230
* is more efficient than using `Z_NO_FLUSH`.
227231
*/
228-
err = inflate(&stream, Z_FINISH);
229-
inflateEnd(&stream);
232+
err = inflate(br->zstream, Z_FINISH);
230233
if (err != Z_STREAM_END) {
231234
err = REFTABLE_ZLIB_ERROR;
232235
goto done;
233236
}
234237
err = 0;
235238

236-
if (stream.total_out + block_header_skip != sz) {
239+
if (br->zstream->total_out + block_header_skip != sz) {
237240
err = REFTABLE_FORMAT_ERROR;
238241
goto done;
239242
}
@@ -242,7 +245,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
242245
reftable_block_done(block);
243246
block->data = br->uncompressed_data;
244247
block->len = sz;
245-
full_block_size = src_len + block_header_skip - stream.avail_in;
248+
full_block_size = src_len + block_header_skip - br->zstream->avail_in;
246249
} else if (full_block_size == 0) {
247250
full_block_size = sz;
248251
} else if (sz < full_block_size && sz < block->len &&
@@ -275,6 +278,8 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
275278

276279
void block_reader_release(struct block_reader *br)
277280
{
281+
inflateEnd(br->zstream);
282+
reftable_free(br->zstream);
278283
reftable_free(br->uncompressed_data);
279284
reftable_block_done(&br->block);
280285
}

reftable/block.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ int block_writer_finish(struct block_writer *w);
5656
/* clears out internally allocated block_writer members. */
5757
void block_writer_release(struct block_writer *bw);
5858

59+
struct z_stream;
60+
5961
/* Read a block. */
6062
struct block_reader {
6163
/* offset of the block header; nonzero for the first block in a
@@ -67,6 +69,7 @@ struct block_reader {
6769
int hash_size;
6870

6971
/* Uncompressed data for log entries. */
72+
z_stream *zstream;
7073
unsigned char *uncompressed_data;
7174
size_t uncompressed_cap;
7275

reftable/reader.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ static int reader_seek_linear(struct table_iter *ti,
459459
* we would not do a linear search there anymore.
460460
*/
461461
memset(&next.br.block, 0, sizeof(next.br.block));
462+
next.br.zstream = NULL;
462463
next.br.uncompressed_data = NULL;
463464
next.br.uncompressed_cap = 0;
464465

0 commit comments

Comments
 (0)