[F52.1] mxaccess-codec: BytesMut output buffer for write encoder

Adds `write_message::encode_to_bytes_mut` (and the timestamped variant)
returning a freshly-allocated `BytesMut`. Allocation count is identical
to `encode` (2 allocs/op for fixed-width scalars); the benefit is
downstream — consumers can `BytesMut::split_to` / `freeze` and forward
the body bytes to a wire-level sink without an intermediate copy.

The body builders (`encode_boolean` / `encode_fixed` / `encode_variable`
/ `encode_array`) were refactored to fill a pre-sized `&mut [u8]`
rather than each allocating their own `Vec<u8>`. The dispatcher
computes the body size up front via small `*_body_size` helpers and
resizes the destination buffer (Vec or BytesMut) once. This is also
the prerequisite refactor for F52.3.

Bench delta in `design/M6-bench-baseline.md` § F52.1; existing
`encode` row unchanged at 2 allocs/op. All 265 round-trip tests
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-06 22:46:02 -04:00
parent c7505f9570
commit 4e76b44391
6 changed files with 385 additions and 95 deletions
@@ -39,7 +39,7 @@ use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicU64, Ordering};
use mxaccess_codec::{
MxReferenceHandle, NmxSubscriptionMessage, write_message, write_message::WriteValue,
write_message, write_message::WriteValue, MxReferenceHandle, NmxSubscriptionMessage,
};
// ---- counting allocator -------------------------------------------------
@@ -202,6 +202,18 @@ fn bench_write_string() -> Row {
})
}
// F52.1 — `BytesMut` output. Same alloc count as `encode`; the benefit is
// downstream zero-copy (consumers can `split_to` / `freeze` without copying
// the body bytes).
fn bench_write_int32_bytes_mut() -> Row {
let handle = make_handle();
let value = WriteValue::Int32(42);
measure("write_message::encode_to_bytes_mut (Int32)", 10_000, || {
let bytes = write_message::encode_to_bytes_mut(&handle, &value, 0, 0).unwrap();
std::hint::black_box(bytes);
})
}
fn bench_subscription_decode() -> Row {
// Build a single-record DataUpdate body once; decode N times.
let body = build_data_update_int32_body(42);
@@ -262,6 +274,7 @@ fn main() {
bench_write_double(),
bench_write_bool(),
bench_write_string(),
bench_write_int32_bytes_mut(),
bench_handle_from_names(),
bench_subscription_decode(),
];