Benchmarks
Benchmarks
Measured numbers for the operations that dominate any ER7 workload, plus the machine they came from and the caveats that make them meaningful.
The inputs
Two synthetic messages — never real patient data — chosen to bracket what production traffic looks like.
| Input | Shape | Size | Segments |
|---|---|---|---|
| small | An ADT^A08: MSH, EVN, PID, PV1 | 177 bytes | 4 |
| large | An ORU^R01 with 200 OBX segments, each followed by an NTE carrying an escape sequence | 21,520 bytes | 402 |
The small message is the shape most interfaces move in bulk. The large one is the shape that decides whether a parser is fast enough for a day’s traffic.
Results
Measured 2026-08-26. Apple M4 Max, macOS 26.6.1, rustc 1.98.0, aarch64-apple-darwin, release profile. Criterion, 100 samples per benchmark; the
figure is the median and the bracket is Criterion’s confidence interval.
Parsing
| Benchmark | Time | Interval | Notes |
|---|---|---|---|
parse/small | 2.64 µs | 2.60 – 2.70 | 63.9 MiB/s · ≈ 378,000 messages/second |
parse/large | 260.6 µs | 257.7 – 264.4 | 78.8 MiB/s · ≈ 3,800 messages/second · ≈ 648 ns/segment |
Writing
| Benchmark | Time | Interval | Notes |
|---|---|---|---|
render/small | 369 ns | 365 – 375 | ≈ 2.7 million messages/second |
render/large | 21.4 µs | 21.1 – 21.7 | ≈ 46,800 messages/second |
render/large_crlf_trailing | 21.2 µs | 21.0 – 21.6 | A non-default terminator costs nothing measurable |
Escape sequences
| Benchmark | Time | Interval | Notes |
|---|---|---|---|
escape/escape_plain | 10.8 ns | 10.6 – 11.0 | A value with nothing to escape — the common case |
escape/escape_delimited | 110.9 ns | 109.1 – 113.0 | A value full of delimiters |
escape/unescape_sequenced | 196.1 ns | 192.6 – 200.3 | A value full of escape sequences to decode |
escape/tokenize_sequenced | 122.5 ns | 119.3 – 127.9 | Iterating the sequences without decoding |
Queries
| Benchmark | Time | Interval | Notes |
|---|---|---|---|
query/subcomponent | 80.3 ns | 78.9 – 81.9 | PID-3.4.2 |
query/last_segment | 149.7 ns | 146.3 – 154.5 | NTE-3 |
query/field | 168.8 ns | 166.7 – 171.3 | PID-3 |
query/all_segments | 7.23 µs | 7.13 – 7.34 | query_all("OBX-5") — 200 matches, ≈ 36 ns each |
Three things are worth carrying away. Parsing gets more efficient per byte as
messages grow, because per-message fixed costs amortise away. Writing is roughly twelve times
cheaper than parsing, which matters for the common integration shape: parse once, edit, write
many. And a single query returns on first match rather than walking the whole
message, so looking up PID-3.4.2 in a 402-segment message costs about what it
costs in a four-segment one.
The plain escape case is the one to watch: escaping a value that needs no escaping costs about ten nanoseconds and does not allocate, which is what keeps whole-message writing cheap.
Running them yourself
The benchmarks live in er7-bench/, a workspace member that is not published. It exists so that er7 itself can keep both [dependencies] and [dev-dependencies] empty — a rule its own test
enforces — while Criterion lives one directory over, where it cannot reach the audit surface
of the crate being measured.
cargo bench -p er7-bench
# Record a baseline, change something, then compare against it.
cargo bench -p er7-bench -- --save-baseline before
cargo bench -p er7-bench -- --baseline before
# One group only.
cargo bench -p er7-bench -- parse Criterion writes an HTML report to target/criterion/report/index.html.
Optimisation history
Changes made because a benchmark said so, rather than because the code looked slow.
| Change | Effect |
|---|---|
821a7dc — stop query walking the whole message to return one value | query/field −85%, query/subcomponent −92%. Single-value
lookup went from proportional to message length to effectively constant. |
How to read these numbers, and how not to
- These are single-machine numbers on fast hardware. An M4 Max is not an interface engine in a hospital data centre. Treat the ratios as the durable finding, and the absolute figures as an upper bound.
- No comparison to another library is claimed. Benchmarking someone else’s library fairly is hard, and benchmarking it unfairly is worse than not doing it. The comparison page compares design and scope instead, without inventing numbers.
- Criterion’s outlier counts are not noise to ignore. Runs on a laptop routinely report 5–9% high-severe outliers from scheduling. Compare against a saved baseline on the same machine rather than against a number on this page.
- Nothing here is a latency guarantee. These are library operations, not an end-to-end interface benchmark; a real feed spends most of its time in I/O, TLS, and the receiving system.
Fuzzing
Performance work is only safe next to correctness work. The crate carries cargo-fuzz targets alongside these benchmarks — parse_roundtrip, escape_roundtrip, and query_paths — and each asserts a property the
specification states rather than merely checking for panics.
cargo +nightly fuzz run parse_roundtrip -- -max_total_time=60 The full write-up, with the method and the raw Criterion output, is BENCHMARKS.md in the
repository.