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.

260 µs to parse a 402-segment lab result
12× cheaper to write a message than to parse it
80 ns for one query, whatever the message length
0 dependencies linked into any of it

The inputs

Two synthetic messages — never real patient data — chosen to bracket what production traffic looks like.

InputShapeSizeSegments
smallAn ADT^A08: MSH, EVN, PID, PV1177 bytes4
largeAn ORU^R01 with 200 OBX segments, each followed by an NTE carrying an escape sequence21,520 bytes402

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

BenchmarkTimeIntervalNotes
parse/small2.64 µs2.60 – 2.7063.9 MiB/s · ≈ 378,000 messages/second
parse/large260.6 µs257.7 – 264.478.8 MiB/s · ≈ 3,800 messages/second · ≈ 648 ns/segment

Writing

BenchmarkTimeIntervalNotes
render/small369 ns365 – 375≈ 2.7 million messages/second
render/large21.4 µs21.1 – 21.7≈ 46,800 messages/second
render/large_crlf_trailing21.2 µs21.0 – 21.6A non-default terminator costs nothing measurable

Escape sequences

BenchmarkTimeIntervalNotes
escape/escape_plain10.8 ns10.6 – 11.0A value with nothing to escape — the common case
escape/escape_delimited110.9 ns109.1 – 113.0A value full of delimiters
escape/unescape_sequenced196.1 ns192.6 – 200.3A value full of escape sequences to decode
escape/tokenize_sequenced122.5 ns119.3 – 127.9Iterating the sequences without decoding

Queries

BenchmarkTimeIntervalNotes
query/subcomponent80.3 ns78.9 – 81.9PID-3.4.2
query/last_segment149.7 ns146.3 – 154.5NTE-3
query/field168.8 ns166.7 – 171.3PID-3
query/all_segments7.23 µs7.13 – 7.34query_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.

ChangeEffect
821a7dc — stop query walking the whole message to return one valuequery/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

  1. 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.
  2. 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.
  3. 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.
  4. 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.