Benchmark Results
Raw benchmark tables for the battle_bench reference suite:
Ferroni (Rust) vs Oniguruma at -O3. This is the primary comparison for an
Oniguruma-compatible engine. Measurements for Rust's regex crate are retained
in a separate shared-syntax appendix.
Measured with Criterion on Apple M1 Pro.
Re-run with
./scripts/prepare-oniguruma-sources.sh && cargo bench --features ffi --bench battle_benchto get current numbers on your hardware, then render the tables below with./scripts/gen_battle_tables.py.
The README intentionally rounds values for readability. This file keeps the
raw numbers, and the home page derives its speed factors from the rows below
(pnpm check:numbers in docs/ enforces that).
Every number on this page comes from the single run recorded in Measurement context. Releases after that commit include further performance work, so treat the tables as dated evidence: refresh them by re-running the suite on the reference machine and updating this page and the README together.
A full comparison for PR #173 records a newer run on an M1 Ultra, including seven everyday regex tasks, opt-in match-cache, and memory measurements. It also corrects the search harness to request equivalent capture-output work from Ferroni and C. Treat it as a separate snapshot, not a regression comparison with the numbers below.
Measurement context
Exact external input revisions for the publishable battle suite are pinned in
benches/battle_inputs.toml.
| Field | Value |
|---|---|
| Ferroni commit | 2f109a75cfb2acd2a583a833a381350296692e38 |
| Measurement date | 2026-09-23 |
| Host | MacBookPro18,1 (Apple M1 Pro, 32 GB) |
| macOS | 27.0 (26A428) |
rustc | rustc 1.98.1 (48a229cea 2026-09-01) |
| Command | FERRONI_ONIGURUMA_DIR=~/Workspace/oniguruma cargo bench --features ffi --bench battle_bench -- --noplot --quiet |
| Oniguruma | f95747b462de672b6f8dbdeb478245ddf061ca53, a local checkout at the pinned commit |
| Input pins | benches/battle_inputs.toml |
Reference suite (battle_bench)
The main tables compare Ferroni and Oniguruma. Bold marks the lower time
between those two engines; the factor is C time divided by Ferroni time.
regex does not support lookarounds or backreferences,
so its shared-syntax results cannot establish Oniguruma compatibility or an
overall engine ranking. They remain available in the appendix. The RegSet and
scanner paths have no equivalent regex measurement in this harness; that is
distinct from unsupported syntax. See the
benchmark scope map
for the features exercised by the current suite.
Text search and log scanning
| Scenario | Ferroni | Oniguruma | C / Ferroni |
|---|---|---|---|
| Literal in 50 KB | 67.919 ns | 130.060 ns | 1.91x |
| No match, 50 KB | 1.505 us | 9.301 us | 6.2x |
| No match, 10 KB | 463.016 ns | 1.907 us | 4.1x |
| Field extract, 50 KB | 93.838 ns | 155.274 ns | 1.65x |
| Timestamp, 50 KB | 100.001 ns | 156.689 ns | 1.57x |
| RegSet multi-pattern (5) | 103.530 ns | 370.259 ns | 3.6x |
Pattern matching
| Category | Ferroni | Oniguruma | C / Ferroni |
|---|---|---|---|
| Literal exact | 98.051 ns | 138.579 ns | 1.41x |
| Quantifier greedy | 158.797 ns | 170.604 ns | 1.07x |
| Lookaround combined | 78.848 ns | 247.484 ns | 3.1x |
Unicode \p{Greek}+ | 99.956 ns | 272.779 ns | 2.7x |
Backref (\w+) \1 | 80.731 ns | 154.555 ns | 1.91x |
| Case-insensitive phrase | 95.805 ns | 177.897 ns | 1.86x |
| Alternation, 2 branches | 64.687 ns | 142.427 ns | 2.2x |
| Alternation, 10 branches | 50.650 ns | 212.268 ns | 4.2x |
| Named capture date | 244.451 ns | 262.994 ns | 1.08x |
Compilation
| Pattern | Ferroni | Oniguruma | C / Ferroni |
|---|---|---|---|
| Literal | 494.262 ns | 469.539 ns | 0.95x |
| Named capture | 3.680 us | 5.941 us | 1.61x |
| Lookbehind | 1.104 us | 591.766 ns | 0.54x |
Scanner with full Shiki TextMate grammars
Full, unmodified grammars from shikijs/textmate-grammars-themes.
These rows are the warm path: every iteration hands the scanner the same
OnigString, so from the second iteration on Ferroni answers fallback patterns
that did not match from its per-string memo. The C scanner gets no such help
here: the vscode-oniguruma wrapper only consults its per-pattern cache for
strings of 1000 bytes or more, and every input in this suite is shorter, so it
runs a full onig_regset_search on every call. The document rows below measure
the cold path a real tokenizer sees.
| Scenario | Ferroni | Oniguruma | Factor |
|---|---|---|---|
| TypeScript (279 patterns) | |||
| Compile | 10.168 ms | 17.266 ms | 1.70x |
| First match, short line | 86.785 ns | 25.224 us | 290.6x |
| Tokenize full line | 2.847 us | 211.428 us | 74.3x |
| CSS (117 patterns) | |||
| Compile | 13.441 ms | 19.411 ms | 1.44x |
| Tokenize (multi-line) | 222.139 us | 14.324 ms | 64.5x |
| Rust (81 patterns) | |||
| Compile | 292.147 us | 190.268 us | 0.65x |
| First match | 173.485 ns | 5.520 us | 31.8x |
| Tokenize full line | 5.057 us | 79.308 us | 15.7x |
Scanner on whole documents, line by line
The TypeScript, CSS and Rust documents from
benches/scanner_documents.rs
and
benches/scanner_css_workload.rs,
tokenized line by line with the trailing newline, each line handed to the
scanner once, the way vscode-textmate and Shiki drive it. Ferroni sees a
distinct OnigString per line and the C scanner a fresh str_cache_id per
line, so neither engine answers a line from the previous one. Times are for the
whole document; before measuring, the suite asserts that both engines produce
the same number of tokens.
| Document | Ferroni | Oniguruma | Factor |
|---|---|---|---|
| TypeScript (279 patterns), 28 lines | 1.261 ms | 3.126 ms | 2.5x |
| CSS (117 patterns), 19 lines | 93.244 us | 3.039 ms | 32.6x |
| Rust (81 patterns), 31 lines | 108.126 us | 1.058 ms | 9.8x |
Shared-syntax context only: regex
These measurements answer a narrower question: how quickly each engine runs
the particular patterns supported by all three. They do not compare the full
Oniguruma feature set or establish whether regex can replace Ferroni.
regex excludes lookarounds and backreferences
as part of its design. Its timings remain useful when an application's
patterns fit that subset. Capture groups and Unicode are supported; those
features alone do not require an Oniguruma-compatible engine.
The original measurements are retained below without winner counts or an overall ranking. Only measured shared-syntax cases appear here. Lookarounds, backreferences, and the unmeasured RegSet/scanner path are excluded explicitly, not assigned a slow or zero result. Times and measurement conditions are unchanged from their corresponding main tables.
Shared syntax: text search and log scanning
| Scenario | Ferroni | Oniguruma | regex (shared syntax only) |
|---|---|---|---|
| Literal in 50 KB | 67.919 ns | 130.060 ns | 9.181 ns |
| No match, 50 KB | 1.505 us | 9.301 us | 1.474 us |
| No match, 10 KB | 463.016 ns | 1.907 us | 296.820 ns |
| Field extract, 50 KB | 93.838 ns | 155.274 ns | 54.981 ns |
| Timestamp, 50 KB | 100.001 ns | 156.689 ns | 52.430 ns |
Shared syntax: pattern matching
| Category | Ferroni | Oniguruma | regex (shared syntax only) |
|---|---|---|---|
| Literal exact | 98.051 ns | 138.579 ns | 10.105 ns |
| Quantifier greedy | 158.797 ns | 170.604 ns | 62.337 ns |
Unicode \p{Greek}+ | 99.956 ns | 272.779 ns | 59.750 ns |
| Case-insensitive phrase | 95.805 ns | 177.897 ns | 59.175 ns |
| Alternation, 2 branches | 64.687 ns | 142.427 ns | 44.859 ns |
| Alternation, 10 branches | 50.650 ns | 212.268 ns | 19.579 ns |
| Named capture date | 244.451 ns | 262.994 ns | 43.230 ns |
Shared syntax: compilation
| Pattern | Ferroni | Oniguruma | regex (shared syntax only) |
|---|---|---|---|
| Literal | 494.262 ns | 469.539 ns | 2.693 us |
| Named capture | 3.680 us | 5.941 us | 209.521 us |
Reproducing
# Reference suite for publishable Ferroni-vs-C numbers
./scripts/prepare-oniguruma-sources.sh
cargo bench --features ffi --bench battle_bench
# or build against an existing checkout at the pinned commit
FERRONI_ONIGURUMA_DIR=/path/to/oniguruma cargo bench --features ffi --bench battle_bench
# Tables in the layout of this page
./scripts/gen_battle_tables.py
# Internal Rust-only regression suite
cargo bench --bench regression_bench
# HTML report
open target/criterion/report/index.html