Skip to content

Frame a record into a buffer that lives longer than the record - #735

Merged
bjmeetsfo merged 1 commit into
mainfrom
mx/reuse-the-frame-buffer
Sep 3, 2026
Merged

Frame a record into a buffer that lives longer than the record#735
bjmeetsfo merged 1 commit into
mainfrom
mx/reuse-the-frame-buffer

Conversation

@bjmeetsfo

Copy link
Copy Markdown
Collaborator

Every append built the payload, then allocated a second buffer to hold the payload plus about ten
bytes of frame, and copied it in. That second allocation is the whole record again, on every write.

The design this is measured against keeps one long-lived operation-log message and a reusable
buffer on its logger rather than minting them per write. This does the same for the frame: the
buffer lives on the log's inner state, encode_record_into clears and refills it, and its capacity
survives.

value allocations an append bytes allocated
64 B 34.0 -> 33.0 9,201 -> 9,096
256 B 33.0 10,172 -> 9,873
1024 B 33.0 14,013 -> 12,950
4096 B 33.9 29,390 -> 25,271 (-14%)

The count drops by one; the bytes drop by roughly the size of a record, so the saving grows with
the payload -- which is the right shape, since it removes the cost that scales.

The buffer is taken with mem::take and returned on the success path, so the rest of the inner
state stays usable while it is borrowed. An early return drops it and the next append allocates
once: correct either way, just not free that once. encode_record is unchanged for its other
callers and now allocates a buffer and delegates.

distributed_raft_chunks_large_sequence_add_under_default_entry_limit failed once in the local
full suite and is not from this: it passes 3/3 in isolation on this branch and 3/3 on main, and it
failed the same way on an unrelated branch earlier in the week.

Every append built the payload, then allocated a second buffer to hold the payload plus about ten
bytes of frame and copied it in. That second allocation is the whole record again, on every write.

The comparable design keeps one long-lived operation-log message and one reusable buffer on its
logger rather than minting them per write. This does the same for the frame: the buffer lives on
the log's inner state, `encode_record_into` clears it and refills it, and its capacity survives.

    value    64 B:  34.0 allocations an append -> 33.0,   9,201 B ->  9,096
    value  4096 B:                                33.9,  29,390 B -> 25,271  (-14%)

The count drops by one; the bytes drop by roughly the size of the record, so the saving grows with
the payload. That is the shape you want here -- the cost it removes is the one that scales.

The buffer is taken with `mem::take` and put back on the success path, so the rest of the inner
state stays usable while it is borrowed. An early return drops it and the next append allocates
once, which is correct, just not free that once.

`encode_record` is unchanged for its other callers; it now allocates a buffer and delegates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@bjmeetsfo
bjmeetsfo force-pushed the mx/reuse-the-frame-buffer branch from cc70b5f to c664369 Compare September 3, 2026 17:01
@bjmeetsfo
bjmeetsfo merged commit 6408bea into main Sep 3, 2026
6 of 7 checks passed
@bjmeetsfo
bjmeetsfo deleted the mx/reuse-the-frame-buffer branch September 3, 2026 17:26
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