refactor(dpi/quic): single-allocation connection_id_to_hex#308
refactor(dpi/quic): single-allocation connection_id_to_hex#3080xghost42 wants to merge 1 commit into
connection_id_to_hex#308Conversation
`connection_id_to_hex` was rendering a QUIC connection ID through the
worst-case allocation pattern: `format!("{:02x}", b)` allocated a
2-byte `String` for every input byte, the iterator collected them into
a `Vec<String>` (one more allocation), and `join(":")` then walked the
vector to build the final `String`. For an 8-byte short-form DCID that
is 8 + 1 + 1 = 10 heap allocations per render.
Replace with a single `String::with_capacity(id.len() * 3 - 1)` (exact
final size: 2 hex chars per byte + N-1 colon separators) and a per-byte
`write!`. Writing into a pre-sized `String` does not reallocate, so the
helper now performs exactly one heap allocation per call regardless of
input length, except for the empty-slice case which returns an empty
`String` (no allocation). The lowercase, colon-separated output is
unchanged.
Adds four regression tests covering:
- An 8-byte representative DCID — locks the lowercase + colon-separated
contract.
- A mix of single-digit bytes (0x00..=0x0f) — locks the zero-padding
contract that `{:02x}` provides.
- A single-byte input — locks that no trailing separator is emitted.
- The empty-slice base case.
Closes domcyrus#305
|
This PR, lik the other, is lacking any proof that this is a performance gain and or that the compiler does not optimize the current code to a single string write. |
|
@laundmo Fair ask. Ran a release-mode micro-bench locally to check both halves of the concern (perf gain + compiler folding). Bench keeps both shapes side-by-side so the comparison is apples-to-apples on the same host: fn old(id: &[u8]) -> String {
id.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<String>>()
.join(":")
}
fn new(id: &[u8]) -> String {
if id.is_empty() { return String::new(); }
let mut out = String::with_capacity(id.len() * 3 - 1);
let mut first = true;
for b in id {
if !first { out.push(':'); }
let _ = write!(out, "{b:02x}");
first = false;
}
out
}
Output bytes identical ( So both legs of the concern resolve as expected:
|
Summary
connection_id_to_hexwas rendering a QUIC connection ID through the worst-case allocation pattern:format!("{:02x}", b)allocated a 2-byteStringfor every input byte, the iterator collected them into aVec<String>(one more allocation), andjoin(":")then walked the vector to build the finalString. For an 8-byte short-form DCID that is 8 + 1 + 1 = 10 heap allocations per render.Replace with a single
String::with_capacity(id.len() * 3 - 1)(exact final size: 2 hex chars per byte + N-1 colon separators) and a per-bytewrite!. Writing into a pre-sizedStringdoes not reallocate, so the helper now performs exactly one heap allocation per call regardless of input length, except for the empty-slice case which returns an emptyString(no allocation). The lowercase, colon-separated output is unchanged.Tests
Four regression tests added:
{:02x}provides.Local checks:
cargo test --lib— 365 passed.cargo clippy --all-targets -- -D warnings— clean.cargo fmt --check— clean.Closes #305