Skip to content

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_bench to 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.

FieldValue
Ferroni commit2f109a75cfb2acd2a583a833a381350296692e38
Measurement date2026-09-23
HostMacBookPro18,1 (Apple M1 Pro, 32 GB)
macOS27.0 (26A428)
rustcrustc 1.98.1 (48a229cea 2026-09-01)
CommandFERRONI_ONIGURUMA_DIR=~/Workspace/oniguruma cargo bench --features ffi --bench battle_bench -- --noplot --quiet
Onigurumaf95747b462de672b6f8dbdeb478245ddf061ca53, a local checkout at the pinned commit
Input pinsbenches/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

ScenarioFerroniOnigurumaC / Ferroni
Literal in 50 KB67.919 ns130.060 ns1.91x
No match, 50 KB1.505 us9.301 us6.2x
No match, 10 KB463.016 ns1.907 us4.1x
Field extract, 50 KB93.838 ns155.274 ns1.65x
Timestamp, 50 KB100.001 ns156.689 ns1.57x
RegSet multi-pattern (5)103.530 ns370.259 ns3.6x

Pattern matching

CategoryFerroniOnigurumaC / Ferroni
Literal exact98.051 ns138.579 ns1.41x
Quantifier greedy158.797 ns170.604 ns1.07x
Lookaround combined78.848 ns247.484 ns3.1x
Unicode \p{Greek}+99.956 ns272.779 ns2.7x
Backref (\w+) \180.731 ns154.555 ns1.91x
Case-insensitive phrase95.805 ns177.897 ns1.86x
Alternation, 2 branches64.687 ns142.427 ns2.2x
Alternation, 10 branches50.650 ns212.268 ns4.2x
Named capture date244.451 ns262.994 ns1.08x

Compilation

PatternFerroniOnigurumaC / Ferroni
Literal494.262 ns469.539 ns0.95x
Named capture3.680 us5.941 us1.61x
Lookbehind1.104 us591.766 ns0.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.

ScenarioFerroniOnigurumaFactor
TypeScript (279 patterns)
Compile10.168 ms17.266 ms1.70x
First match, short line86.785 ns25.224 us290.6x
Tokenize full line2.847 us211.428 us74.3x
CSS (117 patterns)
Compile13.441 ms19.411 ms1.44x
Tokenize (multi-line)222.139 us14.324 ms64.5x
Rust (81 patterns)
Compile292.147 us190.268 us0.65x
First match173.485 ns5.520 us31.8x
Tokenize full line5.057 us79.308 us15.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.

DocumentFerroniOnigurumaFactor
TypeScript (279 patterns), 28 lines1.261 ms3.126 ms2.5x
CSS (117 patterns), 19 lines93.244 us3.039 ms32.6x
Rust (81 patterns), 31 lines108.126 us1.058 ms9.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

ScenarioFerroniOnigurumaregex (shared syntax only)
Literal in 50 KB67.919 ns130.060 ns9.181 ns
No match, 50 KB1.505 us9.301 us1.474 us
No match, 10 KB463.016 ns1.907 us296.820 ns
Field extract, 50 KB93.838 ns155.274 ns54.981 ns
Timestamp, 50 KB100.001 ns156.689 ns52.430 ns

Shared syntax: pattern matching

CategoryFerroniOnigurumaregex (shared syntax only)
Literal exact98.051 ns138.579 ns10.105 ns
Quantifier greedy158.797 ns170.604 ns62.337 ns
Unicode \p{Greek}+99.956 ns272.779 ns59.750 ns
Case-insensitive phrase95.805 ns177.897 ns59.175 ns
Alternation, 2 branches64.687 ns142.427 ns44.859 ns
Alternation, 10 branches50.650 ns212.268 ns19.579 ns
Named capture date244.451 ns262.994 ns43.230 ns

Shared syntax: compilation

PatternFerroniOnigurumaregex (shared syntax only)
Literal494.262 ns469.539 ns2.693 us
Named capture3.680 us5.941 us209.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