Benchmarks · TOML
teptris, the TOML engine
teptris is the TOML counterpart in the leptris ecosystem: a TOML 1.0/1.1 parser and writer in pure C99 with zero required runtime dependencies and a stable C ABI. The benchmark below races it against five widely used C and C++ TOML parsers on a deterministic, seeded corpus of nine document shapes, with parsing measured from memory and destruction timed separately. Every library that provides a key-counting API is cross-checked for agreement on the corpus, so a fast failure cannot masquerade as a fast parse.
| TOML — competitor | C / C++ | Ruby | Python | JavaScript | Rust |
|---|---|---|---|---|---|
| @iarna/toml | — | — | — | 0.94×–4.16× | — |
| @ltd/j-toml | — | — | — | 0.97×–26.07× | — |
| cpptoml | 4.40×+ | — | — | — | — |
| rtoml | — | — | 0.70×–5.99× | — | — |
| smol-toml | — | — | — | 0.74×–2.37× | — |
| toml | — | — | — | — | 3.93×+ |
| toml11 | 14.75×+ | — | — | — | — |
| toml_edit | — | — | — | — | 2.52×+ |
| tomlc17 | 4.40×+ | — | — | — | — |
| tomlc99 | 4.50×+ | — | — | — | — |
| tomli-w | — | — | 6.35×+ | — | — |
| tomlib | — | 3.51×+ | — | — | — |
| tomlkit | — | — | 6.68×+ | — | — |
| tomllib | — | — | 17.62×+ | — | — |
| tomlplusplus | 3.10×+ | — | — | — | — |
| tomlrb | — | 2.94×+ | — | — | — |
A green cell with a single value means every measured operation in that race is ahead, and the value shown is the smallest margin. A neutral cell shows the range from the weakest to the strongest operation; hovering lists the operations that remain behind. All rows, including deficits, appear in the tables below.
C and C++
teptris versus the reference parsers
The competitors span the C and C++ ecosystems: tomlc99 and tomlc17 in C, and cpptoml, toml11 and tomlplusplus in C++. Each is pinned to a recorded commit or release in the benchmark ledger. Two of them do not parse the full corpus: tomlc17 rejects the largest scalar shapes with its own keys-per-table cap, and cpptoml applies TOML v0.5 semantics and rejects mixed-type arrays that are valid in TOML 1.0. Those shapes are excluded from the affected competitor’s rows rather than counted against it.
| operation | leptris side | the reference parsers | standing |
|---|---|---|---|
| Parse — array heavy (738 KB) | 522.1 MB/s | 49.7 MB/s | 10.50× |
| Parse — array heavy (738 KB) | 522.1 MB/s | 0.1 MB/s | 5221.00× |
| Parse — array heavy (738 KB) | 522.1 MB/s | 59.3 MB/s | 8.80× |
| Parse — array heavy (738 KB) | 522.1 MB/s | 95.9 MB/s | 5.40× |
| Parse — cargo like (363 KB) | 371.3 MB/s | 5.4 MB/s | 68.80× |
| Parse — cargo like (363 KB) | 371.3 MB/s | 0.2 MB/s | 1856.50× |
| Parse — cargo like (363 KB) | 371.3 MB/s | 43.9 MB/s | 8.50× |
| Parse — cargo like (363 KB) | 371.3 MB/s | 15.8 MB/s | 23.50× |
| Parse — cargo like (363 KB) | 371.3 MB/s | 84.5 MB/s | 4.40× |
| Parse — datetime heavy (1109 KB) | 540.7 MB/s | 0.4 MB/s | 1351.80× |
| Parse — datetime heavy (1109 KB) | 540.7 MB/s | 1.4 MB/s | 386.20× |
| Parse — datetime heavy (1109 KB) | 540.7 MB/s | 35.6 MB/s | 15.20× |
| Parse — datetime heavy (1109 KB) | 540.7 MB/s | 85.6 MB/s | 6.30× |
| Parse — deep tables (249 KB) | 381.8 MB/s | 85.1 MB/s | 4.50× |
| Parse — deep tables (249 KB) | 381.8 MB/s | 0.2 MB/s | 1909.00× |
| Parse — deep tables (249 KB) | 381.8 MB/s | 43.7 MB/s | 8.70× |
| Parse — deep tables (249 KB) | 381.8 MB/s | 79.5 MB/s | 4.80× |
| Parse — deep tables (249 KB) | 381.8 MB/s | 78.9 MB/s | 4.80× |
| Parse — mixed (446 KB) | 445.2 MB/s | 31.6 MB/s | 14.10× |
| Parse — mixed (446 KB) | 445.2 MB/s | 0.7 MB/s | 636.00× |
| Parse — mixed (446 KB) | 445.2 MB/s | 32.8 MB/s | 13.60× |
| Parse — mixed (446 KB) | 445.2 MB/s | 100.2 MB/s | 4.40× |
| Parse — mixed (446 KB) | 445.2 MB/s | 80.4 MB/s | 5.50× |
| Parse — scalar float (1502 KB) | 367 MB/s | 0.3 MB/s | 1223.30× |
| Parse — scalar float (1502 KB) | 367 MB/s | 1.2 MB/s | 305.80× |
| Parse — scalar float (1502 KB) | 367 MB/s | 21.9 MB/s | 16.80× |
| Parse — scalar float (1502 KB) | 367 MB/s | 53.7 MB/s | 6.80× |
| Parse — scalar int (1307 KB) | 378 MB/s | 0.3 MB/s | 1260.00× |
| Parse — scalar int (1307 KB) | 378 MB/s | 1.1 MB/s | 343.60× |
| Parse — scalar int (1307 KB) | 378 MB/s | 33.8 MB/s | 11.20× |
| Parse — scalar int (1307 KB) | 378 MB/s | 55.5 MB/s | 6.80× |
| Parse — scalar string (1329 KB) | 520.8 MB/s | 1.1 MB/s | 473.50× |
| Parse — scalar string (1329 KB) | 520.8 MB/s | 2.8 MB/s | 186.00× |
| Parse — scalar string (1329 KB) | 520.8 MB/s | 50.1 MB/s | 10.40× |
| Parse — scalar string (1329 KB) | 520.8 MB/s | 87.1 MB/s | 6.00× |
| Parse — table heavy (1150 KB) | 411.2 MB/s | 3.8 MB/s | 108.20× |
| Parse — table heavy (1150 KB) | 411.2 MB/s | 0.5 MB/s | 822.40× |
| Parse — table heavy (1150 KB) | 411.2 MB/s | 36.9 MB/s | 11.10× |
| Parse — table heavy (1150 KB) | 411.2 MB/s | 3.4 MB/s | 120.90× |
| Parse — table heavy (1150 KB) | 411.2 MB/s | 73.9 MB/s | 5.60× |
fresh runteptris 0.3.4 (run 5, best-of-10 min)·min of 10 (Release + LTO), parse from memory·Apple M1 Max, macOS 14.1.1, arm64··bench_parse — parse matrix vs five reference TOML parsers, deterministic seeded corpus
teptris is the fastest parser on every measured shape, with the smallest margin at 2.9 times tomlc17 and the widest margins against toml11, whose generic error-recovery machinery costs two orders of magnitude on large files. The measurements follow three profile-driven optimization rounds recorded, with before-and-after numbers, in theteptris benchmark ledger; the same file records the corpus seed and the exact command that regenerates every number on this page.
To reproduce: run scripts/run_benchmarks.sh in the teptris repository, which builds the Release configuration, regenerates the seeded corpus, and writesbenchmarks/results/bench_parse.json. The harness is committed atbenchmarks.
Ruby
The teptris gem versus tomlib and tomlrb
The Ruby binding provides a tomlib-shaped API over the native engine. The harness parses every corpus file through each library in the same process, reporting the best of twelve iterations after warm-up. tomlib is a pure-Ruby parser and tomlrb is a Ragel-based Ruby extension; both are measured at their pinned release versions.
| operation | leptris side | tomlib / tomlrb | standing |
|---|---|---|---|
| Parse — array heavy | 4.1 ms | 29.03 ms | 7.08× |
| Parse — array heavy | 4.1 ms | 45.11 ms | 11.00× |
| Parse — cargo like | 4.28 ms | 93.29 ms | 21.80× |
| Parse — cargo like | 4.28 ms | 23.05 ms | 5.39× |
| Parse — datetime heavy | 36.45 ms | 14590.56 ms | 400.29× |
| Parse — datetime heavy | 36.45 ms | 128.09 ms | 3.51× |
| Parse — deep tables | 2.74 ms | 9.61 ms | 3.51× |
| Parse — deep tables | 2.74 ms | 19.65 ms | 7.17× |
| Parse — mixed | 5.06 ms | 22.01 ms | 4.35× |
| Parse — mixed | 5.06 ms | 50.64 ms | 10.01× |
| Parse — scalar float | 17.05 ms | 22062.08 ms | 1293.96× |
| Parse — scalar float | 17.05 ms | 112.62 ms | 6.61× |
| Parse — scalar int | 17.53 ms | 16936.16 ms | 966.12× |
| Parse — scalar int | 17.53 ms | 80.03 ms | 4.57× |
| Parse — scalar string | 11.43 ms | 3963.14 ms | 346.73× |
| Parse — scalar string | 11.43 ms | 33.62 ms | 2.94× |
| Parse — table heavy | 12.34 ms | 1129.26 ms | 91.51× |
| Parse — table heavy | 12.34 ms | 91.92 ms | 7.45× |
fresh runteptris-ruby 0.3.6.3 (main, engine v0.3.6 pin)·best of 12 after 2 warm-up iterations·GitHub Actions ubuntu-latest runner (x86_64); Ruby 3.3.12··lang_tier.rb — Teptris::TOML.load vs tomlib and tomlrb over the bench corpus
One observation belongs here rather than in a table: the datetime-heavy corpus costs the engine 3.7 milliseconds through the C API but 142 milliseconds through the Ruby binding, because constructing one Ruby Time object per datetime dominates the binding’s time on that shape. Binding overhead of this kind is inherent to rich object models and is recorded so it stays visible.
To reproduce: runTEPTRIS_LIB_PATH=… bundle exec ruby benchmark/lang_tier.rbin theteptris-rubyrepository.
The dump lane
Serialization measured over materialized corpus documents (materialization untimed): TOML.dump versus Tomlib.dump, the only other Ruby TOML writer, at best-of-40 on Apple silicon. The native-doc path (load_doc/dump_doc, never leaving C) is tabled separately in its own section of the Ruby page.
| operation | leptris side | tomlib | standing |
|---|---|---|---|
| Dump — array heavy | 2.61 ms | 24.15 ms | 9.25× |
| Dump — cargo like | 1.47 ms | 13.78 ms | 9.37× |
| Dump — datetime heavy | 6.13 ms | 22.25 ms | 3.63× |
| Dump — deep tables | 0.9 ms | 7.79 ms | 8.66× |
| Dump — mixed | 2.58 ms | 19.53 ms | 7.57× |
| Dump — scalar float | 9.03 ms | 66.09 ms | 7.32× |
| Dump — scalar int | 6.3 ms | 32.8 ms | 5.21× |
| Dump — scalar string | 4.43 ms | 19.89 ms | 4.49× |
| Dump — table heavy | 7.01 ms | 53.74 ms | 7.67× |
fresh runteptris-ruby 0.3.6.3 (main, engine v0.3.6 pin, PGO build)·best of 40·Apple M1 Max, macOS 14.1.1, arm64; Ruby 3.4.8··benchmark/dump_tier.rb — TOML.dump vs Tomlib.dump over materialized corpus documents (materialization untimed)
C — emit
Serializing back to TOML versus toml11 and tomlplusplus
Every library that has a serializer serializes the parsed document back to TOML; the C section above covers the parse side. tomlc99, tomlc17 and cpptoml have no serializer and are absent for that reason, not by omission. The engine’s natural-JSON mode (the one real hosts consume) is faster still than the TOML emit tabled here.
| operation | leptris side | toml11 / tomlplusplus | standing |
|---|---|---|---|
| Emit — array heavy | 1.226 ms | 607.158 ms | 495.44× |
| Emit — array heavy | 1.226 ms | 42.969 ms | 35.06× |
| Emit — cargo like | 0.738 ms | 29.733 ms | 40.27× |
| Emit — cargo like | 0.738 ms | 2.364 ms | 3.20× |
| Emit — datetime heavy | 1.41 ms | 93.178 ms | 66.10× |
| Emit — datetime heavy | 1.41 ms | 80.123 ms | 56.84× |
| Emit — deep tables | 0.465 ms | 27.634 ms | 59.44× |
| Emit — deep tables | 0.465 ms | 2.15 ms | 4.62× |
| Emit — mixed | 1.198 ms | 28.722 ms | 23.98× |
| Emit — mixed | 1.198 ms | 7.815 ms | 6.53× |
| Emit — scalar float | 5.559 ms | 81.974 ms | 14.75× |
| Emit — scalar float | 5.559 ms | 38.484 ms | 6.92× |
| Emit — scalar int | 2.405 ms | 65.678 ms | 27.31× |
| Emit — scalar int | 2.405 ms | 17.657 ms | 7.34× |
| Emit — scalar string | 2.115 ms | 39.297 ms | 18.58× |
| Emit — scalar string | 2.115 ms | 6.552 ms | 3.10× |
| Emit — table heavy | 3.107 ms | 71.355 ms | 22.96× |
| Emit — table heavy | 3.107 ms | 15.522 ms | 5.00× |
fresh runteptris 0.3.7 (main, Release + LTO)·min of 10 (Release + LTO); only the competitors with serializers are tabled (tomlc99/tomlc17/cpptoml have none)·Apple M1 Max, macOS 14.1.1, arm64··bench_parse emit columns — every library serializes the parsed document back to TOML, min of 10
Zero losses across the 18 cells; the spread is wide because toml11’s emitter is quadratic on some shapes — the ratio column reports what the harness measured, per shape.
To reproduce: runscripts/run_benchmarks.sh in theteptris repository, or the one-command container fromscripts/bench-container/.
Python
The teptris package versus tomllib, tomlkit and rtoml
The Python binding mirrors the standard library’s tomllib interface and is measured under the same protocol as the Ruby tier against the three libraries users actually choose between: tomllib (the stdlib), tomlkit (pure-Python, materializing a mutable document model), and rtoml (a Rust-backed binding).
| operation | leptris side | tomllib / tomlkit / rtoml | standing |
|---|---|---|---|
| Parse — array heavy | 7.34 ms | 239.09 ms | 32.57× |
| Parse — array heavy | 7.34 ms | 1799.59 ms | 245.18× |
| Parse — array heavy | 7.34 ms | 10.89 ms | 1.48× |
| Parse — cargo like | 3.87 ms | 103.08 ms | 26.64× |
| Parse — cargo like | 3.87 ms | 1126.79 ms | 291.16× |
| Parse — cargo like | 3.87 ms | 7.97 ms | 2.06× |
| Parse — datetime heavy | 8.75 ms | 221.09 ms | 25.27× |
| Parse — datetime heavy | 8.75 ms | 1723.41 ms | 196.96× |
| Parse — datetime heavy | 8.75 ms | 52.43 ms | 5.99× |
| Parse — deep tables | 2.24 ms | 83.12 ms | 37.11× |
| Parse — deep tables | 2.24 ms | 16301.93 ms | 7277.65× |
| Parse — deep tables | 2.24 ms | 6.17 ms | 2.75× |
| Parse — mixed | 4.96 ms | 148.77 ms | 29.99× |
| Parse — mixed | 4.96 ms | 1450.75 ms | 292.49× |
| Parse — mixed | 4.96 ms | 13.85 ms | 2.79× |
| Parse — scalar float | 13.24 ms | 302.52 ms | 22.85× |
| Parse — scalar float | 13.24 ms | 2365.92 ms | 178.69× |
| Parse — scalar float | 13.24 ms | 47.73 ms | 3.60× |
| Parse — scalar int | 12.37 ms | 294.04 ms | 23.77× |
| Parse — scalar int | 12.37 ms | 2198.86 ms | 177.76× |
| Parse — scalar int | 12.37 ms | 39.99 ms | 3.23× |
| Parse — scalar string | 6.81 ms | 191.93 ms | 28.18× |
| Parse — scalar string | 6.81 ms | 978.25 ms | 143.65× |
| Parse — scalar string | 6.81 ms | 15.79 ms | 2.32× |
| Parse — table heavy | 19.96 ms | 351.7 ms | 17.62× |
| Parse — table heavy | 19.96 ms | 4085.73 ms | 204.70× |
| Parse — table heavy | 19.96 ms | 56.84 ms | 2.85× |
fresh runteptris-py 0.3.7.1 (published wheel, engine v0.3.7)·best of 12 after 2 warm-up iterations·GitHub Actions ubuntu-latest runner (x86_64); Python 3.12··lang_tier.py — teptris.loads vs tomli.loads over the bench corpus
To reproduce: runTEPTRIS_LIB_PATH=… python3 benchmark/lang_tier.py in theteptris-pyrepository.
The dump lane, measured honestly
Serialization is a separate race with its own record. On this lane teptris keeps the datetime and string shapes against rtoml while rtoml keeps the flat-scalar shapes — and the native-doc path (loads_doc/dumps_doc, which never leaves C) wins the full roundtrip 9 of 9. Losses are tabled, not hidden.
| operation | leptris side | rtoml / tomli-w / tomlkit | standing |
|---|---|---|---|
| Dump — array heavy | 2.61 ms | 2.26 ms | 0.87× |
| Dump — cargo like | 2.04 ms | 2.19 ms | 1.07× |
| Dump — datetime heavy | 4.73 ms | 7.94 ms | 1.68× |
| Dump — deep tables | 1.38 ms | 1.19 ms | 0.86× |
| Dump — mixed | 3.32 ms | 3.01 ms | 0.91× |
| Dump — scalar float | 9.83 ms | 8.05 ms | 0.82× |
| Dump — scalar int | 7.1 ms | 4.98 ms | 0.70× |
| Dump — scalar string | 5.33 ms | 7.07 ms | 1.33× |
| Dump — table heavy | 8.73 ms | 6.52 ms | 0.75× |
| Dump — array heavy | 2.61 ms | 25.29 ms | 9.69× |
| Dump — cargo like | 2.04 ms | 24.18 ms | 11.85× |
| Dump — datetime heavy | 4.73 ms | 47.85 ms | 10.12× |
| Dump — deep tables | 1.38 ms | 13.06 ms | 9.46× |
| Dump — mixed | 3.32 ms | 31.83 ms | 9.59× |
| Dump — scalar float | 9.83 ms | 62.43 ms | 6.35× |
| Dump — scalar int | 7.1 ms | 49.14 ms | 6.92× |
| Dump — scalar string | 5.33 ms | 73.81 ms | 13.85× |
| Dump — table heavy | 8.73 ms | 69.94 ms | 8.01× |
| Dump — array heavy | 2.61 ms | 48.18 ms | 18.46× |
| Dump — cargo like | 2.04 ms | 31.72 ms | 15.55× |
| Dump — datetime heavy | 4.73 ms | 40.1 ms | 8.48× |
| Dump — deep tables | 1.38 ms | 40.32 ms | 29.22× |
| Dump — mixed | 3.32 ms | 48.24 ms | 14.53× |
| Dump — scalar float | 9.83 ms | 65.68 ms | 6.68× |
| Dump — scalar int | 7.1 ms | 69.54 ms | 9.79× |
| Dump — scalar string | 5.33 ms | 47.07 ms | 8.83× |
| Dump — table heavy | 8.73 ms | 104.1 ms | 11.92× |
fresh runteptris-py 0.3.7.1 line (local ext, engine v0.3.7)·best of 40·Apple M1 Max, macOS 14.1.1, arm64··benchmark/dump_tier.py — serialization of the same parsed object vs rtoml.dumps, tomli-w.dumps and tomlkit.dumps (materialization untimed)
JavaScript
The teptris package versus @iarna/toml, smol-toml and @ltd/j-toml
The JS binding runs the engine as WebAssembly and is measured against the three libraries the ecosystem actually installs: @iarna/toml (the long-standing standard), smol-toml (the modern fast one) and @ltd/j-toml. Loads and dumps are both tabled; where a comparator rejects a legal corpus shape the cell is omitted and noted.
| operation | leptris side | @iarna/toml / smol-toml / @ltd/j-toml | standing |
|---|---|---|---|
| Parse — array heavy | 11.9 ms | 10.87 ms | 0.91× |
| Parse — array heavy | 11.9 ms | 143.85 ms | 12.09× |
| Parse — cargo like | 8.53 ms | 20.41 ms | 2.39× |
| Parse — cargo like | 8.53 ms | 9.09 ms | 1.07× |
| Parse — cargo like | 8.53 ms | 53.11 ms | 6.23× |
| Parse — datetime heavy | 24.05 ms | 78.12 ms | 3.25× |
| Parse — datetime heavy | 24.05 ms | 57 ms | 2.37× |
| Parse — datetime heavy | 24.05 ms | 104.01 ms | 4.32× |
| Parse — deep tables | 5 ms | 19.24 ms | 3.85× |
| Parse — deep tables | 5 ms | 6.13 ms | 1.23× |
| Parse — deep tables | 5 ms | 34.48 ms | 6.90× |
| Parse — mixed | 10.63 ms | 14.44 ms | 1.36× |
| Parse — scalar float | 43.72 ms | 137.6 ms | 3.15× |
| Parse — scalar float | 43.72 ms | 65.17 ms | 1.49× |
| Parse — scalar float | 43.72 ms | 122.87 ms | 2.81× |
| Parse — scalar int | 33.27 ms | 138.34 ms | 4.16× |
| Parse — scalar int | 33.27 ms | 63.26 ms | 1.90× |
| Parse — scalar int | 33.27 ms | 127.57 ms | 3.83× |
| Parse — scalar string | 25.52 ms | 57.63 ms | 2.26× |
| Parse — scalar string | 25.52 ms | 26.07 ms | 1.02× |
| Parse — scalar string | 25.52 ms | 67.27 ms | 2.64× |
| Parse — table heavy | 29.23 ms | 95.58 ms | 3.27× |
| Parse — table heavy | 29.23 ms | 47.83 ms | 1.64× |
| Parse — table heavy | 29.23 ms | 155.63 ms | 5.32× |
| Dump — array heavy | 7.26 ms | 6.37 ms | 0.88× |
| Dump — array heavy | 7.26 ms | 20.18 ms | 2.78× |
| Dump — cargo like | 7.78 ms | 21.17 ms | 2.72× |
| Dump — cargo like | 7.78 ms | 6.26 ms | 0.80× |
| Dump — cargo like | 7.78 ms | 105.84 ms | 13.60× |
| Dump — datetime heavy | 63.44 ms | 59.52 ms | 0.94× |
| Dump — datetime heavy | 63.44 ms | 47.12 ms | 0.74× |
| Dump — datetime heavy | 63.44 ms | 64.96 ms | 1.02× |
| Dump — deep tables | 4.2 ms | 8.7 ms | 2.07× |
| Dump — deep tables | 4.2 ms | 3.7 ms | 0.88× |
| Dump — deep tables | 4.2 ms | 109.48 ms | 26.07× |
| Dump — mixed | 11.47 ms | 25.09 ms | 2.19× |
| Dump — mixed | 11.47 ms | 9.77 ms | 0.85× |
| Dump — mixed | 11.47 ms | 211.16 ms | 18.41× |
| Dump — scalar float | 33.32 ms | 100.48 ms | 3.02× |
| Dump — scalar float | 33.32 ms | 26.14 ms | 0.78× |
| Dump — scalar float | 33.32 ms | 50.32 ms | 1.51× |
| Dump — scalar int | 29.33 ms | 92.5 ms | 3.15× |
| Dump — scalar int | 29.33 ms | 24.73 ms | 0.84× |
| Dump — scalar int | 29.33 ms | 52.09 ms | 1.78× |
| Dump — scalar string | 18.74 ms | 46.68 ms | 2.49× |
| Dump — scalar string | 18.74 ms | 15.22 ms | 0.81× |
| Dump — scalar string | 18.74 ms | 18.19 ms | 0.97× |
| Dump — table heavy | 24.79 ms | 56.95 ms | 2.30× |
| Dump — table heavy | 24.79 ms | 27.29 ms | 1.10× |
| Dump — table heavy | 24.79 ms | 420.54 ms | 16.96× |
fresh runteptris (npm) 0.3.11, engine v0.3.6 wasm·best of N (report-only lane); @iarna rejects two legal corpus shapes (pre-1.0 single-type-array rule) and mixed; reject cells are omitted·GitHub Actions ubuntu-latest runner (x86_64); Node 20··benchmark/js_tier.mjs — loads and dumps vs @iarna/toml, smol-toml and @ltd/j-toml over the shared generated corpus
The honest shape of this tier: loads beat smol-toml on 8 of 9 shapes and @iarna everywhere it runs, while smol-toml keeps the dump lane on 8 of 9 shapes — the WASM boundary plus JS object materialization price every serialize. The losses are recorded, not hidden; the native roundtrip (loadsDoc/dumpDoc, staying in C) wins 9 of 9.
To reproduce: runnode benchmark/js_tier.mjs in theteptris-tsrepository.
Rust
The teptris crate versus toml and toml_edit
The Rust binding links the engine through the stable C ABI with zero-copy value materialization, measured against the ecosystem’s standard toml crate and its editing-focused sibling toml_edit across loads, dumps and the full roundtrip.
| operation | leptris side | toml / toml_edit | standing |
|---|---|---|---|
| Parse — array heavy | 6.52 ms | 42.79 ms | 6.56× |
| Dump — array heavy | 2.75 ms | 12.89 ms | 4.69× |
| Roundtrip (load+dump) — array heavy | 6.59 ms | 56.22 ms | 8.53× |
| Roundtrip (load+dump) — array heavy | 6.59 ms | 50.77 ms | 7.70× |
| Parse — cargo like | 3.31 ms | 14.4 ms | 4.35× |
| Dump — cargo like | 2.03 ms | 12.88 ms | 6.34× |
| Roundtrip (load+dump) — cargo like | 5.78 ms | 28.21 ms | 4.88× |
| Roundtrip (load+dump) — cargo like | 5.78 ms | 17.81 ms | 3.08× |
| Parse — datetime heavy | 4.85 ms | 50.02 ms | 10.31× |
| Dump — datetime heavy | 5 ms | 46.15 ms | 9.23× |
| Roundtrip (load+dump) — datetime heavy | 9.91 ms | 97.79 ms | 9.87× |
| Roundtrip (load+dump) — datetime heavy | 9.91 ms | 34.81 ms | 3.51× |
| Parse — deep tables | 2 ms | 11.78 ms | 5.89× |
| Dump — deep tables | 1.24 ms | 9.9 ms | 7.98× |
| Roundtrip (load+dump) — deep tables | 3.29 ms | 22.13 ms | 6.73× |
| Roundtrip (load+dump) — deep tables | 3.29 ms | 15.76 ms | 4.79× |
| Parse — mixed | 3.99 ms | 23.37 ms | 5.86× |
| Dump — mixed | 2.85 ms | 21.26 ms | 7.46× |
| Roundtrip (load+dump) — mixed | 7.36 ms | 47 ms | 6.39× |
| Roundtrip (load+dump) — mixed | 7.36 ms | 29.45 ms | 4.00× |
| Parse — scalar float | 8.26 ms | 69.69 ms | 8.44× |
| Dump — scalar float | 11.09 ms | 47.05 ms | 4.24× |
| Roundtrip (load+dump) — scalar float | 19.36 ms | 118.08 ms | 6.10× |
| Roundtrip (load+dump) — scalar float | 19.36 ms | 69.65 ms | 3.60× |
| Parse — scalar int | 7.42 ms | 72.65 ms | 9.79× |
| Dump — scalar int | 7.95 ms | 34.98 ms | 4.40× |
| Roundtrip (load+dump) — scalar int | 15.31 ms | 110.84 ms | 7.24× |
| Roundtrip (load+dump) — scalar int | 15.31 ms | 72.37 ms | 4.73× |
| Parse — scalar string | 6.46 ms | 27.95 ms | 4.33× |
| Dump — scalar string | 4.76 ms | 23.75 ms | 4.99× |
| Roundtrip (load+dump) — scalar string | 11.3 ms | 52.65 ms | 4.66× |
| Roundtrip (load+dump) — scalar string | 11.3 ms | 28.42 ms | 2.52× |
| Parse — table heavy | 9.07 ms | 67.71 ms | 7.47× |
| Dump — table heavy | 7.97 ms | 63.54 ms | 7.97× |
| Roundtrip (load+dump) — table heavy | 19.12 ms | 142.19 ms | 7.44× |
| Roundtrip (load+dump) — table heavy | 19.12 ms | 90.93 ms | 4.76× |
fresh runteptris (crates.io) 0.1.5, engine v0.3.7 (main)·best of 20·GitHub Actions ubuntu-latest runner (x86_64)··benches/tier.rs — loads, dumps and roundtrip vs the toml crate and toml_edit over the shared generated corpus
The widest gap in the family: no language-boundary tax means the engine’s margin passes through intact — 4–11× on loads, 3–6× on dumps and 6–10× on the roundtrip against the standard crate, zero losses across all 36 cells.
To reproduce: runcargo bench --bench tier -- corpus 20 in theteptris-rsrepository.
The serde lane
The ecosystem’s dominant API shape, measured head-to-head: both crates’ derives deserialize the same 415 KB struct-shaped document (2000 array-of-tables entries against 8 typed fields). The derive layer preserves essentially the whole engine margin — and the direct-into-struct follow-up is bounded by the measured Value→struct walk, 11.6% of the path, recorded rather than pursued.
| operation | leptris side | toml 0.8 serde path | standing |
|---|---|---|---|
| Parse — derive into structs | 2.29 ms | 11.28 ms | 4.93× from_str::<Doc> (derive) |
| Parse — Value model | 2.03 ms | 11.5 ms | 5.67× loads -> Value |
| Dump — to_string | 2.56 ms | 10.05 ms | 3.93× to_string(&Doc) |
fresh runteptris (crates.io) 0.1.6 - the serde feature (default on)·best of 20; the Value->struct walk is 0.26 ms (11.6% of the derive path) - the direct-into-struct fusion ceiling, recorded not pursued·Apple M1 Max, macOS 14.1.1, arm64··benches/serde_tier.rs - derive-vs-derive over a 415 KB struct-shaped document (2000 array-of-tables entries x 8 typed fields), best-of-20
TOML
Measured deficits
Every TOML operation in the result data that remains behind its competitor. There are none at present; this section is generated from the same result files as the tables above and will list any deficit that a future measurement records.
| operation | leptris side | versus | standing |
|---|---|---|---|
| Dump — scalar int | 7.1 ms | 4.98 ms | 0.70× |
| Dump — datetime heavy | 63.44 ms | 47.12 ms | 0.74× |
| Dump — table heavy | 8.73 ms | 6.52 ms | 0.75× |
| Dump — scalar float | 33.32 ms | 26.14 ms | 0.78× |
| Dump — cargo like | 7.78 ms | 6.26 ms | 0.80× |
| Dump — scalar string | 18.74 ms | 15.22 ms | 0.81× |
| Dump — scalar float | 9.83 ms | 8.05 ms | 0.82× |
| Dump — scalar int | 29.33 ms | 24.73 ms | 0.84× |
| Dump — mixed | 11.47 ms | 9.77 ms | 0.85× |
| Dump — deep tables | 1.38 ms | 1.19 ms | 0.86× |
| Dump — array heavy | 2.61 ms | 2.26 ms | 0.87× |
| Dump — array heavy | 7.26 ms | 6.37 ms | 0.88× |
| Dump — deep tables | 4.2 ms | 3.7 ms | 0.88× |
| Dump — mixed | 3.32 ms | 3.01 ms | 0.91× |
| Parse — array heavy | 11.9 ms | 10.87 ms | 0.91× |
| Dump — datetime heavy | 63.44 ms | 59.52 ms | 0.94× |
| Dump — scalar string | 18.74 ms | 18.19 ms | 0.97× |