# Performance
These are local reference measurements, not CI thresholds.
## Method
- Linux x86_64, AMD Ryzen 9 3900X.
- Release build with a fixed fixture generator.
- `cargo bench -p adotenv --bench core --locked -- --sample-size 20 --measurement-time 3`
- The host is shared. Treat wall-clock values as approximate and prefer
instruction counts when comparing CPU work.
- Re-run on an idle host before making a performance decision.
## Core workloads
| parse | 6.07 us | 87.4 us | 92.8 ms |
| lookup index build | 125 ns | 2.81 us | 15.6 ms |
| indexed get, 64 keys | 194 ns | 1.69 us | 1.62 us |
| portability, 4 presets | 46.2 us | 822 us | 843 ms |
| references | 513 ns | 8.91 us | 5.20 ms |
| resolve | 6.79 us | 133 us | 161 ms |
| sort | 3.12 us | 247 us | 436 ms |
| format normalize | 8.26 us | 150 us | 187 ms |
| native set | 8.57 us | 91.4 us | 80.3 ms |
| native batch | 7.12 us | 75.6 us | 106.6 ms |
`Document::get` scans entries. For many lookups, build `Document::lookup_index`
once. The index uses a compact open-addressed table with an ordered fallback for
collision-heavy input.
## Parser memory
One 65,536-entry document with a 1.8 MB source:
- live after parse: 23.6 MB;
- peak parse: 32.0 MB;
- parse plus collected map: 4.6 MB live after the document is dropped.
Retained document memory is dominated by item and token metadata. Values-only
loading avoids the document entirely.
## Loading memory
One 65,536-entry file on a release build:
| Default | 47.3 MB | 27.8 MB |
| `LoadOptions::full` | 57.1 MB | 37.6 MB |
| Values only | 11.2 MB | 9.9 MB |
The default keeps values, shared-file origins and raw tokens. Reports add entry,
override, repeat and skipped vectors. Values-only loading projects values while
parsing and retains no document or provenance.
The default profile now stores value, origin and raw-token data in one record
per key. This removes two per-key maps from the retained loader.
## Budgets
The comparison harness is the source of these budgets. Run the full check with:
```console
python3 scripts/compare.py --check-budgets
```
| adotenv parse | 260M | not budgeted | 29 MB |
| adotenv parse + values | 400M | 12 MB | 12 MB |
| adotenv parse + map | 335M | not budgeted | 16 MB |
| values-only load | 400M | 13 MB | 11.5 MB |
| default load | 560M | 50 MB | 30 MB |
| full-report load | 750M | 60 MB | 40 MB |
The parse-only binary budget is 500 KB. Wall time is recorded but not enforced
because the shared host is too noisy for a hard threshold.
A release blocks above the instruction or memory budget until the regression is
fixed or explicitly accepted. A wall-time result between 1.5x and 2x requires a
repeat run and an explanation; above 2x blocks the release.
## Diff and matrix
For three 65,536-entry files:
- streaming two-file diff with indexed comparison: about 73.3 MB peak and 0.6 s;
- matrix diff with compact incremental rows: about 103.0 MB peak and 0.8 s.
Matrix output retains one row per key because every column is needed. Two-file
diff streams candidates and buffers output until all candidates validate.
## Comparison with other parsers
The closest comparison is `adotenv::parse` followed by collecting effective
values. Other crates return maps or load into the environment directly.
Fixture: 65,536 compatible declarations, 1.8 MB source.
| adotenv parse | 272M | 23.6 MB | 32.0 MB |
| adotenv parse + map | 377M | 4.6 MB | 32.0 MB |
| dotenvy iterator + Vec | 373M | 5.0 MB | 15.7 MB |
| dotenv-compat parse | 307M | 9.5 MB | 18.6 MB |
| dotenv-ng input-only load | 406M | 7.9 MB | 20.4 MB |
| dotenv-parser map | 1,488M | 7.1 MB | 50.1 MB |
adotenv retains more syntax than the other parsers, including spans, items and
diagnostics. Values-only loading removes that overhead when it is not needed.
## Budget
- CI runs benchmark smoke only.
- Compare repeated runs on the same host and toolchain.
- A 1.5x deviation needs a repeat run and an explanation.
- A 2x deviation blocks a release until it is explained or fixed.
- Update this file when fixtures, compiler major version or methodology change.