Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion advanced/binary-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ pub enum NodeStr<'a> {
pub struct NodeRef<'a> {
pub tag: NodeStr<'a>, // Borrowed or inline
pub attrs: AttrsRef<'a>, // Vec<(NodeStr<'a>, ValueRef<'a>)>
pub content: Option<Box<NodeContentRef<'a>>>,
pub content: Option<NodeContentRef<'a>>,
}

pub enum NodeContentRef<'a> {
Expand All @@ -670,6 +670,14 @@ pub enum NodeContentRef<'a> {
`NodeStr` replaces the previous `Cow<'a, str>` used in `NodeRef`, `AttrsRef`, `ValueRef`, and `NodeContentRef`. The key difference is that the `Owned` variant uses `CompactString` (inline up to 24 bytes) instead of `String` (always heap-allocated), reducing allocation pressure for the many short protocol strings that can't be statically interned.
</Note>

<Note>
`NodeRef::content` dropped its `Box` — it is now `Option<NodeContentRef<'a>>` rather than `Option<Box<NodeContentRef<'a>>>`. If you match on it with `.as_deref()`, use `.as_ref()` instead. If you hold a `Box<NodeContentRef<'_>>` directly, it still dereferences the same as before.

The box existed only to keep `NodeRef::content` pointer-sized, but for the commonest node in a fanout — a leaf with `Cow::Borrowed` byte content, which already allocates nothing — the box was the *only* allocation. Removing it cuts allocations per decode by 40% on a fanout (45 → 27) and drops native decode time by 11.2% on both the fanout and large-stanza benchmarks. `size_of::<NodeRef>()` grows from 48 to 72 bytes in exchange (on 64-bit platforms), which does not regress any measured shape, including one with no content to begin with.

`NodeContentRef::Nodes` keeps its own `Box<NodeVec<'a>>`: without it, the recursive child-node type would have no finite size. Removing that box isn't part of this change.
</Note>

Location: `wacore/binary/src/node.rs:10-106`, `465-469`, `437-441`

### Node nesting depth cap
Expand Down Expand Up @@ -936,6 +944,12 @@ The `spilled()` method on `SmallVec` reports whether a given node's attrs overfl

Location: `wacore/binary/src/node.rs`, `wacore/binary/tests/attrs_inline_alloc.rs`

### Unboxed `NodeRef` content

`NodeRef::content` (`Option<NodeContentRef<'a>>`) has no `Box` around the inner value — see the note under [Zero-copy decoding](#zero-copy-decoding). On `bench_unmarshal_fanout`, this drops allocations per decode from 45 to 27 and instructions by 9.5% under callgrind (the instrument CodSpeed uses), alongside an 11.2% native wall-time improvement on both the fanout and large-stanza benchmarks. `bench_unmarshal_small`, which carries no content, is unaffected either way — the gain tracks how many nodes in the tree carry content, not a fixed per-node cost.

Location: `wacore/binary/src/node.rs`, `wacore/binary/benches/binary_benchmark.rs`

## Common protocol patterns

### IQ (info/query) stanzas
Expand Down