Benchmarks
hyperlark builds its parse tables at runtime (like chevrotain and
tree-sitter) — it does not generate code. So its natural peers are other
runtime parsers, not codegen parsers (nom, lalrpop, pest) or specialized
native parsers (JSON.parse), which sit in their own tiers. Every measurement is
like-for-like: each parser produces and reads the same value (the JSON
object, or the evaluated calc result) through its natural pipeline.
For a per-language, head-to-head comparison against the main parsing libraries — what each one is, its trade-offs, and when to prefer it — see Alternatives, which charts hyperlark’s LALR and Earley engines against the field in Rust, Python, JS, and C.
The Python hyperlark is the PyO3 binding over the same Rust core: you call
it from Python, but the parse runs in native code — only your transformer
callbacks cross back. So its peers are the other Python parsing libraries. Every
one here computes the same scalar (JSON: the sum of truncated numbers; calc:
the evaluated result) and the outputs are compared, with any disagreement
reported, so the times are like-for-like.
Both engines, one chart. hyperlark’s LALR and Earley appear together so you can see what the general engine costs you. lark’s own Earley is left out here — at 1056 and 1568 ms it would flatten everything — and gets its own comparison at the bottom of this tab.
lark is the pure-Python reference implementation hyperlark reimplements — the parity baseline, on the same grammars. sly is a yacc-style LALR generator that reduces straight to a value, so every library here is measured that way: an embedded transformer, no tree on either side. The three are doing the same shape of work. parsimonious is a PEG library — general, and charted with the other general engines at the foot of this tab. hyperlark wins because the parse itself is native Rust.
Every library, both workloads (ms; JSON 134 KB, calc 85 KB — the bench corpus):
| library | JSON → value | calc → number |
|---|---|---|
| hyperlark (LALR) | 4.85 | 7.05 |
| sly | 43.4 | 39.8 |
| lark (LALR) | 52.6 | 70.8 |
| hyperlark (Earley) | 66.1 | 69.5 |
| parsimonious (PEG) | 201 | 127 |
| lark (Earley) | 1056 | 1568 |
These are hyperlark’s default configuration — opt-in switches (fast_scan)
widen the gap further on scan-heavy inputs. Reproduce with
bindings/python/bench/bench_cli.py, which also checks every library against
hyperlark’s result and reports any that disagrees (lark 1.3.1,
CPython 3.14.4).
Building the parser
Section titled “Building the parser”Construction is faster on every grammar, by 1.7× to 5.6×. The widest margin is on python, much the
largest grammar here, where table construction dominates — but size alone does
not order the rest, and the 85-byte escaped grammar gains more than either
mid-size one:
| Grammar (LALR) | hyperlark | lark | ratio |
|---|---|---|---|
| JSON | 2.74 ms | 4.66 ms | 1.7× |
calc | 2.02 ms | 4.18 ms | 2.1× |
| escaped-string | 0.72 ms | 2.42 ms | 3.4× |
Python (python.lark) | 45.5 ms | 255 ms | 5.6× |
From the same sweep as the parse figures above (_results/python.json), so
these are one run on one machine — trust the shape rather than the digits.
Construction matters most for short-lived processes, where lark pays for table
construction on every start.
Addendum: Earley against lark’s Earley
Section titled “Addendum: Earley against lark’s Earley”Both engines above are compared against lark’s LALR, which is the conservative reading. Engine for engine, against the Earley that lark runs by default — with parsimonious between them as a yardstick, since a general PEG library is the kind of thing people reach for when they want this much generality:
The core, called directly. Its peers split three ways: code generators (nom is hand-written combinators, lalrpop and pest generate a parser at build time), another runtime engine (tree-sitter), and hyperlark’s own two engines. hyperlark builds its tables at runtime, so grammars stay loadable and editable with no codegen step — that flexibility is the trade for the constant factor against the code generators.
See Alternatives → Rust for what each library is and when to prefer it.
The wasm binding, which backs both the JavaScript and TypeScript surfaces. This is the one place a hand-tuned native-JS parser can lead: hyperlark pays the wasm tier tax on the parse itself, plus the cost of building a JS value per node. Same result, honest overhead — and it is the tier and the value building, not a crossing per callback: reductions are batched and drained once per chunk.
The corpus above is structure-heavy — many small nodes, where hyperlark pays the wasm tier tax on the parse plus the cost of building a JS value per node. On scan-heavy input — few nodes, long tokens (large string or number fields, shallow nesting), where the lexer does most of the work — the native Rust scanner pulls ahead:
Why the spread depends on warmup and shape: chevrotain’s JS JIT has to warm
up; wasm is compiled at load. Cold and one-shot workloads flatter wasm; warm,
server-style workloads flatter chevrotain. Every number on this page is warm
(best-of-N minimum), so the charts are the case that flatters chevrotain.
Reproduce with node bench/bench_comparators.mjs in bindings/wasm/, after
npm install in bindings/wasm/bench/ — the comparators live in their own
manifest so the shipped package stays dependency-free.
Addendum: Earley against nearley
Section titled “Addendum: Earley against nearley”nearley is the JS Earley parser, so it is hyperlark’s like-for-like opponent on the general engine — the same pairing as lark’s Earley in the Python tab.
nearley wins the like-for-like. Worth stating plainly rather than charting the flattering configuration: on equal lexical generality it is 1.3× ahead. Delivery is not the excuse. The harness measures both shapes: on calc the no-tree path is 5.3 ms and handing back a JS tree costs 7.01 ms, so tree delivery is 1.71 ms against an Earley figure of 66.8 ms. The cost is the engine and the dynamic lexer.
There is no JSON comparison here: the bench registers no nearley JSON grammar, so the pair has only ever been measured on calc.
The C ABI over the same core, against the classic C parser generators and the
other runtime engine. C is LALR only — it does not expose Earley. Like
bison and lemon, it folds inline during the parse (lark_parse_fold) and builds
no tree, so all three are doing the same shape of work.
Run it on your machine
Section titled “Run it on your machine”Parse a corpus on your own hardware — run it in your browser. Treat those numbers as “how it feels here,” not an authoritative leaderboard — browsers coarsen timers and JIT warmup varies, so the controlled numbers above remain the reference.