git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 8/9] reftable/block: open-code call to `uncompress2()`

From
Patrick Steinhardt <ps@pks.im>
Date
Mar 27, 2024, 06:37 UTC
Message-ID
<bf4c1ab797d32284b469034c5272383fd998439e.1711519925.git.ps@pks.im>
In-Reply-To
<cover.1711519925.git.ps@pks.im>

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>
---
 reftable/block.c | 38 ++++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/reftable/block.c b/reftable/block.c
index 31af075c1d..31e7255056 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -195,10 +195,10 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 	}
 
 	if (typ == BLOCK_TYPE_LOG) {
-		int block_header_skip = 4 + header_off;
-		uLongf dst_len = sz - block_header_skip; /* total size of dest
-							    buffer. */
-		uLongf src_len = block->len - block_header_skip;
+		uint32_t block_header_skip = 4 + header_off;
+		uLong dst_len = sz - block_header_skip;
+		uLong src_len = block->len - block_header_skip;
+		z_stream stream = {0};
 
 		/* Log blocks specify the *uncompressed* size in their header. */
 		REFTABLE_ALLOC_GROW(br->uncompressed_data, sz,
@@ -207,15 +207,33 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		/* Copy over the block header verbatim. It's not compressed. */
 		memcpy(br->uncompressed_data, block->data, block_header_skip);
 
-		/* Uncompress */
-		if (Z_OK !=
-		    uncompress2(br->uncompressed_data + block_header_skip, &dst_len,
-				block->data + block_header_skip, &src_len)) {
+		err = inflateInit(&stream);
+		if (err != Z_OK) {
 			err = REFTABLE_ZLIB_ERROR;
 			goto done;
 		}
 
-		if (dst_len + block_header_skip != sz) {
+		stream.next_in = block->data + block_header_skip;
+		stream.avail_in = src_len;
+		stream.next_out = br->uncompressed_data + block_header_skip;
+		stream.avail_out = dst_len;
+
+		/*
+		 * We know both input as well as output size, and we know that
+		 * the sizes should never be bigger than `uInt_MAX` because
+		 * blocks can at most be 16MB large. We can thus use `Z_FINISH`
+		 * here to instruct zlib to inflate the data in one go, which
+		 * is more efficient than using `Z_NO_FLUSH`.
+		 */
+		err = inflate(&stream, Z_FINISH);
+		inflateEnd(&stream);
+		if (err != Z_STREAM_END) {
+			err = REFTABLE_ZLIB_ERROR;
+			goto done;
+		}
+		err = 0;
+
+		if (stream.total_out + block_header_skip != sz) {
 			err = REFTABLE_FORMAT_ERROR;
 			goto done;
 		}
@@ -224,7 +242,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
 		reftable_block_done(block);
 		block->data = br->uncompressed_data;
 		block->len = sz;
-		full_block_size = src_len + block_header_skip;
+		full_block_size = src_len + block_header_skip - stream.avail_in;
 	} else if (full_block_size == 0) {
 		full_block_size = sz;
 	} else if (sz < full_block_size && sz < block->len &&
-- 
2.44.GIT
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 13 of 31 in “reftable: optimize table and block iterators”
  1. 0/9 reftable: optimize table and block iteratorsPatrick Steinhardt, Mar 27, 2024
  2. 1/9 reftable/block: rename `block_reader_start()`Patrick Steinhardt, Mar 27, 2024
  3. 2/9 reftable/block: merge `block_iter_seek()` and `block_reader_seek()`Patrick Steinhardt, Mar 27, 2024
  4. 3/9 reftable/block: better grouping of functionsPatrick Steinhardt, Mar 27, 2024
  5. 4/9 reftable/block: introduce `block_reader_release()`Patrick Steinhardt, Mar 27, 2024
  6. Karthik NayakApr 3, 2024
  7. Patrick SteinhardtApr 8, 2024
  8. 5/9 reftable/block: move ownership of block reader into `struct table_iter`Patrick Steinhardt, Mar 27, 2024
  9. Justin ToblerApr 3, 2024
  10. Patrick SteinhardtApr 3, 2024
  11. 6/9 reftable/reader: iterate to next block in placePatrick Steinhardt, Mar 27, 2024
  12. 7/9 reftable/block: reuse uncompressed blocksPatrick Steinhardt, Mar 27, 2024
  13. 8/9 reftable/block: open-code call to `uncompress2()`Patrick Steinhardt, Mar 27, 2024
  14. 9/9 reftable/block: reuse `zstream` state on inflationPatrick Steinhardt, Mar 27, 2024
  15. Karthik NayakApr 3, 2024
  16. 00/10 reftable: optimize table and block iteratorsPatrick Steinhardt, Apr 8, 2024
  17. 01/10 reftable/block: rename `block_reader_start()`Patrick Steinhardt, Apr 8, 2024
  18. 02/10 reftable/block: merge `block_iter_seek()` and `block_reader_seek()`Patrick Steinhardt, Apr 8, 2024
  19. 03/10 reftable/block: better grouping of functionsPatrick Steinhardt, Apr 8, 2024
  20. 04/10 reftable/block: introduce `block_reader_release()`Patrick Steinhardt, Apr 8, 2024
  21. 05/10 reftable/block: move ownership of block reader into `struct table_iter`Patrick Steinhardt, Apr 8, 2024
  22. 06/10 reftable/reader: iterate to next block in placePatrick Steinhardt, Apr 8, 2024
  23. 07/10 reftable/block: reuse uncompressed blocksPatrick Steinhardt, Apr 8, 2024
  24. 08/10 reftable/block: open-code call to `uncompress2()`Patrick Steinhardt, Apr 8, 2024
  25. 09/10 reftable/block: reuse `zstream` state on inflationPatrick Steinhardt, Apr 8, 2024
  26. Karthik NayakApr 10, 2024
  27. 10/10 reftable/block: avoid copying block iterators on seekPatrick Steinhardt, Apr 8, 2024
  28. Justin ToblerApr 9, 2024
  29. Patrick SteinhardtApr 9, 2024
  30. Justin ToblerApr 9, 2024
  31. Karthik NayakApr 10, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.