Parser Benchmarks
V2 benchmark reports preserve source revisions, compiler flags, output checks, and raw measurements. Results describe the recorded workloads and machines.
Native engine comparison
The current local cores, v2 e93394e and v1 4e15141, were rerun with
explicit syntax and renderer flags on the original 57 documents (37–113,609
UTF-8 bytes). Each scored column uses the same input set and equivalent HTML
for every included engine. Speed relative to v2: higher is faster; v2 = 1.00×.
| Engine | Fresh, 14 agreeing across six | Reuse, same 14 | Fresh, 50 agreeing across five | Reuse, same 50 |
|---|---|---|---|---|
| Ferromark v2 | 1.00× | 1.00× | 1.00× | 1.00× |
| OX-Content original | 1.00× | 0.98× | — | — |
| Ferromark v1 | 0.71× | 0.71× | 0.53× | 0.54× |
| md4c | 0.26× | 0.21× | 0.31× | 0.28× |
| pulldown-cmark | 0.46× | 0.38× | 0.42× | 0.39× |
| Bun native bun_md | 0.17× | 0.12× | 0.18× | 0.16× |
On the all-six agreement subset, v2 runs at 1.000× OX's throughput fresh and 1.018× with reuse. On the broader five-engine subset it runs at 1.90× v1's speed fresh and 1.85× with reuse. These are measured corpus aggregates; ratios close to 1.00 are not evidence of a universal lead.
The all-six subset contains ten comments and four plain-prose views. The broader five-engine subset also covers technical docs, linked encyclopedia excerpts, references, and READMEs. Both agreeing subsets span 37–80,966 bytes. OX has no score in the five-engine columns: its original renderer cannot disable heading IDs, callouts, inline TOCs, or fence metadata cleanup. Its 39 heading-ID-only differences are not normalized away. The seven cases outside five-engine agreement remain measured as diagnostics, including task CSS and link/content differences.
All engines parse and render natively. The CommonMark lane disables optional syntax; the extension lane enables only tables, strikethrough, and task lists. It is not full GFM. Bare URL autolinking, footnotes, frontmatter, line comments, definition lists, MDX, and optional renderer extras are off where configurable. Raw HTML passes through; these explicit benchmark settings differ from library defaults.
Fresh includes parser/renderer setup, complete processing, owned output, and
destruction. Reuse retains state where the public API permits; Bun's native
bun_md still uses its fresh owned-output API. No JavaScript, WASM, process
startup, file I/O, or output normalization is timed. One macOS arm64 executable,
one Rust compiler, shared mimalloc and the same pinned dependency lock; three
process rounds with six rotating windows per round. Small differences on this
shared workstation are not established significance.
Selected groups, fresh, using only the five-engine agreement subset and the same speed scale:
| Group, five-engine agreement | N | V1 | md4c | pulldown | Bun native |
|---|---|---|---|---|---|
| <512 B | 10 | 0.71× | 0.26× | 0.55× | 0.26× |
| 32–128 KiB | 7 | 0.57× | 0.29× | 0.37× | 0.10× |
| comments | 11 | 0.74× | 0.28× | 0.55× | 0.25× |
| technical-docs | 21 | 0.51× | 0.35× | 0.40× | 0.18× |
| plain-prose | 4 | 0.65× | 0.26× | 0.30× | 0.06× |
Full results and per-document timings, exact flags, and HTML differences include raw measurements, source hashes, and reproducible configuration. The previous matched-flags run and original six-engine run remain historical evidence. The full 207-case before/after suite uses a separate harness and reports all four stages.
The historical two-engine broad Markdown comparison measures 57 cases from 37 bytes to 114 KB: short comments, real documentation, and Wikipedia-derived prose. It separates input size, content, output agreement, and fresh/reused lifecycles. Full tables and raw data and the earlier synthetic diagnostic comparison use the same pinned parser binaries.