-
Notifications
You must be signed in to change notification settings - Fork 0
Internals Zero Copy Payloads
Every captured frame paid two heap copies on the hot path:
-
parse_packet()copied the transport payload out ofPacket.dataintoParsedPacket.payload(udp.payload().to_vec()). -
parse_sip()copied the payload again intoSipMessage.raw(data.to_vec()).
At capture rates this is thousands of allocations/second that exist only to move ownership.
The obvious ParsedPacket<'a> borrowing from Packet.data does not fit
the architecture: Packets cross a crossbeam channel from the capture
thread to the processing thread, ParsedPackets outlive their Packet
inside the reassembler, and SipMessages outlive everything inside the
dialog store. Borrowed payloads would force the backing buffer's lifetime
onto every downstream structure (and SipMessage.raw borrowing from a
sibling field is a self-referential struct).
-
Packet.data: Bytes— oneVec -> Bytesconversion at capture time (zero-copy take-over of the allocation). -
ParsedPacket.payload: Bytes—data.slice(range): refcount bump + offset, no copy. Reassembled datagrams (which genuinely build new buffers) becomeBytes::from(vec)— still no extra copy. -
SipMessage.raw: Bytes— shares the same backing buffer (payload.clone()is a refcount bump).
Bytes derefs to [u8], so consumers that read &pp.payload compile
unchanged; only construction sites changed. Buffers free when the last
clone drops — a stored SipMessage keeps its backing frame alive, which
is the same memory the old design held as an owned copy.
Same-binary A/B isolating the changed operation on a 160-byte payload:
-
payload_slice_zero_copy(Bytes::slice): 15.6 ns -
payload_copy_to_vec(heap copy): 15.1 ns
Honest conclusion: at typical SIP/RTP packet sizes the heap copy was
already as cheap as the refcounted slice — the analysis claim of a
20-30% hot-path win is refuted. The change is cost-neutral on the
single-threaded hot path (packet_decap/eth_ipv4_udp_160b ~127 ns
total either way, within environment noise on a loaded host).
What the design actually buys:
- Large payloads stop costing linear copies (a TCP-reassembled 64 KB SIP message or max-size HEP packet is a ~1-2 us copy; the slice stays ~15 ns).
- No per-packet allocate/free pair crossing the capture -> processing thread boundary (cross-thread free is the allocator's worst case; invisible to a single-threaded benchmark).
- Enables
SipMessage.raw/.bodyto share the packet buffer (done:parse_sip_bytes), removing the second copy of every SIP message and makingSipMessage::clone(dialog-store insertion) copy-free.
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
Using the TUI
CLI & automation
Configuration
Integrations (API & MCP)
Development & internals