Skip to content

Reuse Builder buffer for zero-alloc tight-loop encoding - #9

Merged
khanaffan merged 1 commit into
mainfrom
feat/cpp-builder-buffer-reuse
Jun 22, 2026
Merged

Reuse Builder buffer for zero-alloc tight-loop encoding#9
khanaffan merged 1 commit into
mainfrom
feat/cpp-builder-buffer-reuse

Conversation

@khanaffan

Copy link
Copy Markdown
Owner

Summary

Adds a buffer-reuse API to msgpack::Builder so a single Builder can encode many varying blobs in a tight loop while reusing one heap allocation — no per-iteration malloc.

Motivation

Builder::build() moves the internal buffer into the returned Blob, so a Builder built and discarded each iteration allocates a fresh buffer every time. Hot serialization loops (sockets, files, DB rows) paid a malloc/free per blob.

API

Method Description
Builder& reset() noexcept Rewind to empty, keeping the heap allocation (capacity)
Builder& reserve(size_t bytes) Pre-grow the buffer to skip warm-up growth
size_t capacity() const noexcept Inspect the retained allocation

reset() is built on std::vector::clear(), which keeps capacity. Combined with the existing zero-copy buf_data()/buf_size() accessors, the loop performs zero allocations after warm-up. build() is unchanged and still hands off ownership for one-shot use.

Builder b;
b.reserve(64);
for (const auto& item : items) {
    b.reset();                       // rewind, keep the allocation
    b.map_header(2)
      .string("id").integer(item.id)
      .string("v").real(item.value);
    sink(b.buf_data(), b.buf_size()); // consume the zero-copy view
}

Also removes the dead private reserve(size_t) helper (never called) to free the name for the public capacity-reserve.

Tests

  • New test_builder_buffer_reuse() proves reset() keeps capacity, reserve() grows it, reset() chains, and a 100-iteration loop reuses the same allocation (stable buf_data() pointer + capacity) while producing correct varying blobs.
  • Unit tests: 320 -> 632 passing, 0 failed.
  • Fuzz corpus and C++<->SQLite interop tests: green.
  • Clean build under -Wall -Wextra.

Docs

  • cpp/README.md: new "Buffer reuse (tight loops)" section + usage example; updated low-level accessors and Finalize notes.
  • Test counts updated in cpp/README.md and root README.md.

Add Builder::reset(), reserve(size_t) and capacity() so a single Builder
can encode many varying blobs in a hot loop while reusing one heap
allocation instead of mallocing each iteration.

- reset() rewinds via std::vector::clear() (keeps capacity); paired with
  the existing zero-copy buf_data()/buf_size() accessors, the loop does
  zero per-iteration allocations after warm-up.
- reserve(size_t) pre-grows the buffer; capacity() inspects it.
- build() still moves the buffer out for one-shot ownership handoff.
- Removed the dead private reserve(size_t) helper (never called).

Tests: new test_builder_buffer_reuse proves reset keeps capacity, reserve
grows it, and a 100-iteration loop reuses the same allocation (stable data
pointer + capacity) while producing correct varying blobs. Unit tests
320 -> 632 passing; fuzz corpus and C++<->SQLite interop tests green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@khanaffan khanaffan changed the title feat(cpp): reuse Builder buffer for zero-alloc tight-loop encoding Reuse Builder buffer for zero-alloc tight-loop encoding Jun 22, 2026
@khanaffan
khanaffan merged commit 9e10609 into main Jun 22, 2026
6 checks passed
@khanaffan
khanaffan deleted the feat/cpp-builder-buffer-reuse branch June 22, 2026 17:10
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.

1 participant