Skip to content

fix: read the binary encoding in one call - #20

Open
sveitser wants to merge 2 commits into
mainfrom
fix/read-binary-encoding-in-one-call
Open

fix: read the binary encoding in one call#20
sveitser wants to merge 2 commits into
mainfrom
fix/read-binary-encoding-in-one-call

Conversation

@sveitser

@sveitser sveitser commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

0.1.1 fixed the write side to emit the blob via serialize_bytes, but the read side still went through serde's per-element Vec<u8> path, collecting a sequence one byte at a time. Decode was the more expensive direction: 4.63 ms vs 3.95 ms encode for a 4 MiB payload under bincode.

Switch the binary branch to serde_bytes::deserialize, which requests the whole blob via deserialize_byte_buf: one allocation, one copy. At 4 MiB this is 73 us, a 63x improvement, and decode now matches encode.

Wire format is unchanged for length-prefixed formats (bincode, postcard). Self-describing formats that distinguish byte strings from arrays (CBOR, MessagePack) now emit a byte string; reads still accept either. The docs claimed the binary branch used "the default Vec serialization", which stopped being true on the write side in 0.1.1 and is now false on both; update lib.rs and README.md accordingly.

Note the allocation profile change on the deserialize rustdoc: the buffer is now sized from the format's length prefix before any bytes are read, so readers of untrusted streams must bound it themselves.

Add a regression test using a deserializer that serves only deserialize_bytes/deserialize_byte_buf and errors otherwise, and a criterion benchmark comparing this crate against a plain Vec<u8> field across bincode and JSON at 1 KiB to 4 MiB.

0.1.1 fixed the write side to emit the blob via `serialize_bytes`, but the
read side still went through serde's per-element `Vec<u8>` path, collecting
a sequence one byte at a time. Decode was the more expensive direction:
4.63 ms vs 3.95 ms encode for a 4 MiB payload under bincode.

Switch the binary branch to `serde_bytes::deserialize`, which requests the
whole blob via `deserialize_byte_buf`: one allocation, one copy. At 4 MiB
this is 73 us, a 63x improvement, and decode now matches encode.

Wire format is unchanged for length-prefixed formats (bincode, postcard).
Self-describing formats that distinguish byte strings from arrays (CBOR,
MessagePack) now emit a byte string; reads still accept either. The docs
claimed the binary branch used "the default Vec<u8> serialization", which
stopped being true on the write side in 0.1.1 and is now false on both;
update lib.rs and README.md accordingly.

Note the allocation profile change on the `deserialize` rustdoc: the buffer
is now sized from the format's length prefix before any bytes are read, so
readers of untrusted streams must bound it themselves.

Add a regression test using a deserializer that serves only
`deserialize_bytes`/`deserialize_byte_buf` and errors otherwise, and a
criterion benchmark comparing this crate against a plain `Vec<u8>` field
across bincode and JSON at 1 KiB to 4 MiB.
Binary deserialization now sizes its buffer from the format's length prefix
before reading, so an unbounded `bincode::deserialize_from` over a socket
allocates whatever the peer claims: 12 bytes of input naming 4 GiB costs
4 GiB of RSS and 2 s, against 1.3 MB and 14 us on the old per-byte path,
which serde capped at 1 MiB of preallocation via `size_hint::cautious`.

There is no fix above the format layer. Both `deserialize_bytes` and
`deserialize_byte_buf` route to bincode's `fill_buffer`, which resizes
before `read_exact`; only the per-byte sequence path avoids it, and that is
the 63x slower read this change exists to remove.

bincode charges the length against its size limit before allocating
(`de/mod.rs:93-97`), so a reader with a limit rejects the claim in ~1 us
without allocating. Add a test pinning that, so the mitigation the docs
point callers at cannot silently rot, and document the exposure in the
README rather than only in the rustdoc.
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