Skip to main content

Performance

Binary: Attrs backed by SmallVec for inline storage (#819) Attrs previously used a plain Vec as its backing store, so every node on the encode path paid one heap allocation just for the attribute buffer. In a steady-state send/receive profile this was ~7 allocations per message stanza. On a group fanout it was two per recipient (to + enc). Attrs now uses AttrsVecSmallVec<[(Cow<'static, str>, NodeValue); 2]> — so nodes carrying ≤2 attributes keep them on the stack alongside the node with zero heap allocation. Nodes with ≥3 attributes spill to the heap as before. Measured impact (iai-callgrind, instruction counts):
BenchmarkDelta
marshal_allocating (typical stanza)−6.6% instructions
marshal_reusing_buffer−6.5% instructions
marshal_many_children_allocating (2048 children)−9.9% instructions, −10% estimated cycles, −25% RAM hits
marshal_long_string+4.4% (+208 instructions, content-dominated shape)
unmarshal / unpack / attr_parserunchanged
Allocation counts per iteration (counting allocator): message-shaped stanza 15→11 (−27%); group stanza with 800 participants 4012→2412 (−40%), bytes allocated −22%.

Breaking changes

Attrs.0 type (wacore-binary) Attrs.0 changes from Vec<(Cow<'static, str>, NodeValue)> to AttrsVec. Construction via Attrs::new(), Attrs::with_capacity(), NodeBuilder, iteration, and the serde representation are all unchanged (the serde wire format is byte-identical). Code that held a reference to Attrs.0 typed as Vec needs updating:
// Before
let v: &Vec<_> = &attrs.0;

// After — use AttrsVec directly, or convert:
let v: Vec<_> = attrs.0.into_vec();
AttrsVec is re-exported as wacore_binary::node::AttrsVec. The spilled() method on SmallVec reports whether the list has overflowed to the heap. IqError::Disconnected now carries Box<Node> Both wacore::request::IqError and the main crate’s mirror change Disconnected(Node) to Disconnected(Box<Node>). The larger Node size after the SmallVec inline array triggered clippy’s large_enum_variant; boxing the rare disconnect payload is correct regardless.
// Before
IqError::Disconnected(node) => { /* node: Node */ }

// After
IqError::Disconnected(boxed) => { let node = *boxed; }