Skip to content

Use the internal write buffer more#2

Open
carlhoerberg wants to merge 2 commits into
masterfrom
buffer-writes
Open

Use the internal write buffer more#2
carlhoerberg wants to merge 2 commits into
masterfrom
buffer-writes

Conversation

@carlhoerberg

Copy link
Copy Markdown

Let liblz4 add to the internal buffer until it's full. Decreases the number of writes done to the output IO.

@carlhoerberg

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes Compress::LZ4::Writer#write to batch multiple LZ4F_compressUpdate outputs into the existing @buffer and only write to the underlying @output when the remaining buffer capacity is insufficient (and once at the end). This reduces the number of @output.write calls when a single write call processes multiple MaxSrcSize chunks.

Changes:

  • Accumulate compressed output in @buffer across iterations of the write loop using an in-method buffer_pos offset.
  • Flush @buffer to @output only when the next update’s worst-case bound won’t fit in the remaining space, and once at the end of write.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
@84codes 84codes deleted a comment from Claude AI Jun 25, 2026
The buffer was sized to exactly one worst-case block
(compress_bound(MaxSrcSize)), so the per-iteration fit check always
flushed after a single block and the internal buffering was a no-op.

Size the buffer to hold several blocks (>= 256 KB) and use the
precomputed @block_bound constant for the fit check, which also drops a
redundant compress_bound FFI call per block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carlhoerberg

Copy link
Copy Markdown
Author

Fix: buffer was never actually batching writes

While reviewing, I found the buffering optimization was effectively a no-op.

@buffer was sized to compress_bound(MaxSrcSize) — exactly one worst-case block. So for every full 64 KB chunk, required_buffer_size == @buffer.size, and the fit check

if required_buffer_size > @buffer.size - buffer_pos

reduced to @buffer.size > @buffer.size - buffer_pos, i.e. true whenever buffer_pos > 0. The buffer therefore flushed after every single block and never accumulated more than one — the same one-write-per-block I/O pattern as before, just deferred by one iteration.

Changes (8af27dd)

  • Size the buffer for multiple blocks: Bytes.new(Math.max(@block_bound, MinBufferSize)) with MinBufferSize = 256 KB. Now several worst-case 64 KB blocks (and hundreds of blocks of compressible data) accumulate before a flush. For frames configured with large block sizes, @block_bound exceeds the floor so the buffer still fits at least one block.
  • Use the precomputed @block_bound in the fit check instead of recomputing compress_bound(src_size) every iteration. @block_bound is the worst case for any src_size <= MaxSrcSize, so blocks genuinely accumulate until the buffer is nearly full, and a redundant FFI call per block is removed.

Safety is preserved: on a flush, buffer_pos resets to 0 and the next compress_update gets the full @buffer.size (>= @block_bound >= compress_bound(src_size)) of capacity, so the dst buffer can't overflow.

Net effect: large or highly-compressible writes now issue far fewer, larger writes to the wrapped IO. All specs pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants