reftable/block: open-code call to `uncompress2()`

The reftable format stores log blocks in a compressed format. Thus, whenever we want to read such a block we first need to decompress it. This is done by calling the convenience function `uncompress2()` of the zlib library, which is a simple wrapper that manages the lifecycle of the `zstream` structure for us. While nice for one-off inflation of data, when iterating through reflogs we will likely end up inflating many such log blocks. This requires us to reallocate the state of the `zstream` every single time, which adds up over time. It would thus be great to reuse the `zstream` instead of discarding it after every inflation. Open-code the call to `uncompress2()` such that we can start reusing the `zstream` in the subsequent commit. Note that our open-coded variant is different from `uncompress2()` in two ways: - We do not loop around `inflate()` until we have processed all input. As our input is limited by the maximum block size, which is 16MB, we should not hit limits of `inflate()`. - We use `Z_FINISH` instead of `Z_NO_FLUSH`. Quoting the `inflate()` documentation: "inflate() should normally be called until it returns Z_STREAM_END or an error. However if all decompression is to be performed in a single step (a single call of inflate), the parameter flush should be set to Z_FINISH." Furthermore, "Z_FINISH also informs inflate to not maintain a sliding window if the stream completes, which reduces inflate's memory footprint." Other than that this commit is expected to be functionally equivalent and does not yet reuse the `zstream`. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 8, 2024 at 14:16 UTC 15a60b747e4f0e0d11353f8e89bc9ce7b36c5512
1 file changed +28 -10
reftable/block.c
+28 -10
@@ -195,10 +195,10 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
195 }
196
197 if (typ == BLOCK_TYPE_LOG) {
198 - int block_header_skip = 4 + header_off;
199 - uLongf dst_len = sz - block_header_skip; /* total size of dest
200 - buffer. */
201 - uLongf src_len = block->len - block_header_skip;
198 + uint32_t block_header_skip = 4 + header_off;
199 + uLong dst_len = sz - block_header_skip;
200 + uLong src_len = block->len - block_header_skip;
201 + z_stream stream = {0};
202
203 /* Log blocks specify the *uncompressed* size in their header. */
204 REFTABLE_ALLOC_GROW(br->uncompressed_data, sz,
@@ -207,15 +207,33 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
207 /* Copy over the block header verbatim. It's not compressed. */
208 memcpy(br->uncompressed_data, block->data, block_header_skip);
209
210 - /* Uncompress */
211 - if (Z_OK !=
212 - uncompress2(br->uncompressed_data + block_header_skip, &dst_len,
213 - block->data + block_header_skip, &src_len)) {
210 + err = inflateInit(&stream);
211 + if (err != Z_OK) {
212 err = REFTABLE_ZLIB_ERROR;
213 goto done;
214 }
215
218 - if (dst_len + block_header_skip != sz) {
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 +
221 + /*
222 + * We know both input as well as output size, and we know that
223 + * the sizes should never be bigger than `uInt_MAX` because
224 + * blocks can at most be 16MB large. We can thus use `Z_FINISH`
225 + * here to instruct zlib to inflate the data in one go, which
226 + * is more efficient than using `Z_NO_FLUSH`.
227 + */
228 + err = inflate(&stream, Z_FINISH);
229 + inflateEnd(&stream);
230 + if (err != Z_STREAM_END) {
231 + err = REFTABLE_ZLIB_ERROR;
232 + goto done;
233 + }
234 + err = 0;
235 +
236 + if (stream.total_out + block_header_skip != sz) {
237 err = REFTABLE_FORMAT_ERROR;
238 goto done;
239 }
@@ -224,7 +242,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
242 reftable_block_done(block);
243 block->data = br->uncompressed_data;
244 block->len = sz;
227 - full_block_size = src_len + block_header_skip;
245 + full_block_size = src_len + block_header_skip - stream.avail_in;
246 } else if (full_block_size == 0) {
247 full_block_size = sz;
248 } else if (sz < full_block_size && sz < block->len &&