Skip to content

Regex Benchmarks

How quickly can each engine validate text, extract captures, replace matches or split graphemes? These comparisons use patterns and test inputs from real projects, from simple expressions to complex patterns and advanced Oniguruma features. Every engine has to return the required output before it is timed.

The results come from repeated Blacksmith measurements on ARM64/macOS and x86-64/Linux. They measure isolated regex steps. Complete applications, TextMate Scanner calls and Ferriki highlighting have separate boundaries.

Results at a glance

Choose the syntax your application needs

Ferroni brings Oniguruma’s pattern behavior to a Rust-only build. PCRE2 also supports lookaround, backreferences and recursion; the dialect and implementation language still differ. Rust regex offers guaranteed linear-time matching with a smaller syntax. These are selected features of the configurations used in the comparison.

Selected regex features · checked with untimed compatibility examples
FeatureFerroniRustOnigurumaCRuby OnigmoCPCRE2Cfancy-regexRustRust regexRustregexrRust
Unicode character propertiesYesYesYesYesYesYesYes
Lookahead and lookbehindYesYesYesYesYesNoYes
Unbounded variable-length lookbehindYesYesNoNoYesNoYes
Multi-character Unicode case foldingYesYesYesNoNoNoNo
Unicode word segmentation · UAX #29YesYesNoNoNoNoNo
BackreferencesYesYesYesYesYesNoYes
Recursive subexpression callsYesYesYesYesYesNoNo
Grapheme clusters · \XYesYesYesYesNoNoYes
Absent repeater · (?~...)YesYesYesNoYesNoNo
Oniguruma recursion-level backreferencesYesYesYesNoNoNoNo

Yes means the illustrated syntax returns the expected match. No means that syntax is rejected or its behavior differs; a rewritten expression may solve the same task. PCRE2 uses UTF and UCP, and its interpreter and JIT both pass its supported examples. fancy-regex uses Oniguruma mode; regexr uses Unicode mode with JIT off. This is a feature comparison, not a claim that all accepted patterns have identical semantics. On small screens, scroll the matrix horizontally.

Variable-length lookbehind here includes an unbounded \s*. PCRE2 supports bounded variable-length lookbehind. Multi-character case folding means, for example,strasse matching straße with case ignored. Unicode word segmentation uses UAX #29 rather than the ordinary \b word-character boundary.

The performance corpus measures real regex steps and covers only part of this syntax. Absent repeaters and recursion-level backreferences have no timings in that corpus. Ferroni’s value is preserving Oniguruma behavior in Rust, with current Unicode data and a TextMate Scanner API.

Sources: Oniguruma syntax, PCRE2 syntax, Rust regex, regexr 0.6.1. The executable matrix checks use the pinned dependencies and native sources. See compatibility and scope.

Against C Oniguruma, Ferroni’s standard API is 1.40× as fast on ARM/macOS and 1.13× as fast on x86/Linux in the aggregate for repeated use. Individual cases tell a more varied story: 20 of 21 favor Ferroni on ARM. On Linux, 9 favor Ferroni, 7 favor C, and 5 are inconclusive.

Ferroni is the reference in every cell. 1× means equal throughput. Green values above 1 mean Ferroni is faster; red values below 1 mean Ferroni is slower. For example, 0.50× means half the throughput and 2× means twice the throughput.

Repeated use · Ferroni’s relative throughput
Compared with FerroniARM64 / macOSx86-64 / Linux
C OnigurumaC1.402×21/21 cases1.127×21/21 cases
PCRE2 JITC0.285×21/21 cases0.258×21/21 cases
PCRE2 interpreterC0.832×21/21 cases0.784×21/21 cases
Rust regexRust0.665×10/21 cases0.672×10/21 cases
fancy-regex, seek offRust1.012×17/21 cases0.998×17/21 cases
fancy-regex, seek onRust1.012×17/21 cases0.994×17/21 cases
Ruby OnigmoC1.586×20/21 cases1.513×20/21 cases
regexr, JIT offRust6.955×10/21 cases7.436×10/21 cases
regexr, JIT enabledRust7.231×10/21 cases7.360×10/21 cases

Each row compares its own shared tasks; rows with different case pools are not a leaderboard. Coverage appears below each factor. Task families have equal weight. These figures describe the selected corpus, not a production traffic mix or a guarantee for every pattern.

Preparation changes the comparison. C Oniguruma takes about 9% less time to compile on ARM and 13% less on Linux in this corpus. PCRE2 JIT leads every source-backed reuse case on both platforms; fancy-regex has a similar aggregate reuse time to Ferroni. The tested Rust regex configuration covers fewer of these tasks and offers guaranteed linear-time matching for its supported syntax.

The API path also matters. Ferroni’s standard methods can reuse cached match workspace. Fallible *_with searches with empty options currently create fresh workspace and take about 28% more time on ARM and 29% more on Linux for reuse. Both paths return the same checked output; replacement uses the standard API in both. The detailed tables show this profile separately. Use the fallible methods when your application needs execution errors returned explicitly.

Blacksmith run: October 14, 2026. 21 source-backed regex tasks, 513 test inputs, 3 process repetitions on one VM per platform. Only engines that return the required output are timed. Scanner calls, full highlighting and memory usage are outside this measurement.

In the separate o200k_base pre-tokenization case, regexr’s JIT-off profile reaches 1.20× Ferroni’s throughput on ARM and 1.21× on Linux. These tokenizer results have their own scope and do not enter the 21-task aggregate above.

regexr uses a separate paired Blacksmith run with a freshly measured Ferroni baseline. Unicode mode is enabled. Its JIT-enabled selector can fall back to a non-JIT backend; the detailed evidence records the choice.

Inspect individual cases

Choose a platform, phase and comparator to inspect the work that matters to your application. Compile measures preparing one pattern. Compile + first use adds its first input. Steady reuse applies a prepared regex to the complete input batch. These phases measure different amounts of work; their absolute times cannot be compared as though each were one search.

The baseline is Ferroni's standard public API. Select Ferroni error-aware API to compare the fallible *_with methods directly. Unsupported syntax, different output and inconclusive timings remain visible in the tables.

21 common production cases across 12 families. Pair-specific family-weighted ratio: 1.402×. Controlled probes are excluded from this aggregate. Different supported case pools do not form an engine ranking.

Cases favoring Ferroni's standard API in every run: 20; favoring the comparator: 1; inconclusive: 0.

Ratios are comparator time / Ferroni standard API time. Above 1 favors the standard API; below 1 favors the comparator. Times are medians of 3 process mean estimates; ratios are medians of the ratios paired by repetition. Run ranges retain every process result.

Green factors favor Ferroni, red factors favor the comparator. Inconclusive cases stay neutral even when their point estimate falls above or below 1.

On narrow screens, scroll the table horizontally to compare timings and interpretation.

macos-arm64 · Steady reuse · complete fixture batch · Ferroni standard API and C Oniguruma
WorkloadFerroni standard APIRustC OnigurumaCFerroni relative throughputInterpretation and scope
uuid shapebasic · Boolean match19 inputs974.77 nsRuns: 953.19 ns–1.04 µs2.78 µsRuns: 2.76 µs–2.97 µs2.852×Runs: 2.849×–2.893×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 2.770×–2.959×.

Time interval envelopes (95% per process). Ferroni standard API: 942.99 ns–1.05 µs. C Oniguruma: 2.73 µs–2.99 µs.

uuid v4basic · Boolean match11 inputs504.24 nsRuns: 465.93 ns–537.3 ns1.05 µsRuns: 961.42 ns–1.06 µs2.063×Runs: 1.951×–2.102×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.906×–2.165×.

Time interval envelopes (95% per process). Ferroni standard API: 459.45 ns–544.37 ns. C Oniguruma: 953.48 ns–1.07 µs.

ipv4 validationcomplex · Boolean match12 inputs1.05 µsRuns: 955.59 ns–1.07 µs1.57 µsRuns: 1.45 µs–1.58 µs1.499×Runs: 1.480×–1.515×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.440×–1.573×.

Time interval envelopes (95% per process). Ferroni standard API: 944.15 ns–1.08 µs. C Oniguruma: 1.42 µs–1.61 µs.

ipv6 validationcomplex · Boolean match16 inputs3.27 µsRuns: 3.25 µs–3.7 µs3.74 µsRuns: 3.6 µs–4.16 µs1.125×Runs: 1.100×–1.150×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.080×–1.196×.

Time interval envelopes (95% per process). Ferroni standard API: 3.2 µs–3.75 µs. C Oniguruma: 3.56 µs–4.22 µs.

semver version parsecomplex · Captures at offset27 inputs6.91 µsRuns: 6.89 µs–6.93 µs7.71 µsRuns: 7.27 µs–7.84 µs1.119×Runs: 1.051×–1.131×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.030×–1.167×.

Time interval envelopes (95% per process). Ferroni standard API: 6.82 µs–7.01 µs. C Oniguruma: 7.2 µs–8 µs.

semver hyphen range capturescomplex · First match and captures9 inputs2.08 µsRuns: 1.85 µs–2.09 µs2.39 µsRuns: 2.25 µs–2.47 µs1.179×Runs: 1.147×–1.220×Favors Ferroni standard API in every run
Scope and evidence

The HYPHENRANGE capture step only; excludes hyphenReplace and later range canonicalization.

Ratio interval envelope: 1.110×–1.246×.

Time interval envelopes (95% per process). Ferroni standard API: 1.82 µs–2.12 µs. C Oniguruma: 2.24 µs–2.51 µs.

jinja string tokencomplex · Captures at offset9 inputs995.44 nsRuns: 948.19 ns–1.08 µs1.59 µsRuns: 1.45 µs–1.66 µs1.533×Runs: 1.527×–1.594×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.494×–1.629×.

Time interval envelopes (95% per process). Ferroni standard API: 942.71 ns–1.09 µs. C Oniguruma: 1.43 µs–1.68 µs.

jinja integer tokencomplex · Captures at offset19 inputs1.98 µsRuns: 1.77 µs–2 µs2.23 µsRuns: 2.08 µs–2.36 µs1.177×Runs: 1.113×–1.193×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.066×–1.235×.

Time interval envelopes (95% per process). Ferroni standard API: 1.76 µs–2.07 µs. C Oniguruma: 2.07 µs–2.41 µs.

jinja float tokenadvanced · Captures at offset28 inputs2.65 µsRuns: 2.64 µs–2.72 µs2.85 µsRuns: 2.81 µs–2.91 µs1.067×Runs: 1.066×–1.075×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.037×–1.109×.

Time interval envelopes (95% per process). Ferroni standard API: 2.61 µs–2.76 µs. C Oniguruma: 2.78 µs–2.95 µs.

django email local partadvanced · Boolean match58 inputs2.94 µsRuns: 2.78 µs–3 µs8.41 µsRuns: 7.87 µs–8.75 µs2.827×Runs: 2.808×–2.973×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 2.715×–3.053×.

Time interval envelopes (95% per process). Ferroni standard API: 2.76 µs–3.02 µs. C Oniguruma: 7.73 µs–8.88 µs.

django email domainadvanced · Boolean match58 inputs301.58 µsRuns: 278.19 µs–304.89 µs444.49 µsRuns: 427.64 µs–454.11 µs1.474×Runs: 1.403×–1.632×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.356×–1.663×.

Time interval envelopes (95% per process). Ferroni standard API: 276.2 µs–307.75 µs. C Oniguruma: 417.16 µs–459.41 µs.

django url regexadvanced · Boolean match195 inputs2.03 msRuns: 1.96 ms–2.13 ms2.36 msRuns: 2.21 ms–2.41 ms1.131×Runs: 1.126×–1.162×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.089×–1.193×.

Time interval envelopes (95% per process). Ferroni standard API: 1.93 ms–2.15 ms. C Oniguruma: 2.17 ms–2.44 ms.

fluent bit apachecomplex · Captures at offset4 inputs817.94 nsRuns: 795.86 ns–820.12 ns1.28 µsRuns: 1.25 µs–1.31 µs1.567×Runs: 1.519×–1.649×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.488×–1.698×.

Time interval envelopes (95% per process). Ferroni standard API: 786.92 ns–828.66 ns. C Oniguruma: 1.23 µs–1.34 µs.

fluent bit nginxcomplex · Captures at offset5 inputs1.26 µsRuns: 1.26 µs–1.32 µs2.11 µsRuns: 2.04 µs–2.29 µs1.677×Runs: 1.550×–1.808×Favors Ferroni standard API in every run
Scope and evidence

The isolated source regex call on upstream test inputs; excludes surrounding application logic.

Ratio interval envelope: 1.510×–1.856×.

Time interval envelopes (95% per process). Ferroni standard API: 1.24 µs–1.35 µs. C Oniguruma: 2.03 µs–2.31 µs.

rails underscore regex stepadvanced · Replacement string11 inputs3.02 µsRuns: 2.95 µs–3.03 µs3.25 µsRuns: 3.23 µs–3.72 µs1.096×Runs: 1.073×–1.231×Favors Ferroni standard API in every run
Scope and evidence

String#gsub of one input identifier with the zero-width boundary regexp and literal replacement _. This is the regex step only; preceding namespace/acronym substitution and subsequent tr/downcase are outside it.

Ratio interval envelope: 1.042×–1.255×.

Time interval envelopes (95% per process). Ferroni standard API: 2.91 µs–3.08 µs. C Oniguruma: 3.2 µs–3.76 µs.

mastodon hashtag regex stepadvanced · All matches and captures13 inputs4.38 µsRuns: 4.25 µs–4.63 µs7.21 µsRuns: 6.76 µs–7.71 µs1.647×Runs: 1.591×–1.668×Favors Ferroni standard API in every run
Scope and evidence

text.scan(Tag::HASHTAG_RE) inside Extractor.extract_hashtags_with_indices, after the ASCII/full-width hash precheck. This case returns all raw regexp captures; URL suffix trimming and entity character-index construction are outside this regex step.

Ratio interval envelope: 1.561×–1.714×.

Time interval envelopes (95% per process). Ferroni standard API: 4.2 µs–4.69 µs. C Oniguruma: 6.71 µs–7.81 µs.

python markdown smart emphasis readvanced · Captures at offset5 inputs1.22 µsRuns: 1.18 µs–1.27 µs1.57 µsRuns: 1.44 µs–1.6 µs1.266×Runs: 1.221×–1.287×Favors Ferroni standard API in every run
Scope and evidence

item.pattern.match(data, m.start(0)) in UnderscoreProcessor/AsteriskProcessor after the potential delimiter is found. Match is anchored at the candidate position within the original subject, rather than searched globally.

Ratio interval envelope: 1.177×–1.352×.

Time interval envelopes (95% per process). Ferroni standard API: 1.16 µs–1.33 µs. C Oniguruma: 1.43 µs–1.65 µs.

python markdown smart strong em readvanced · Captures at offset2 inputs1.03 µsRuns: 1.01 µs–1.07 µs907.76 nsRuns: 856.77 ns–916.51 ns0.857×Runs: 0.849×–0.878×Favors comparator in every run
Scope and evidence

item.pattern.match(data, m.start(0)) in UnderscoreProcessor/AsteriskProcessor after the potential delimiter is found. Match is anchored at the candidate position within the original subject, rather than searched globally.

Ratio interval envelope: 0.831×–0.902×.

Time interval envelopes (95% per process). Ferroni standard API: 993.92 ns–1.08 µs. C Oniguruma: 851.46 ns–927.48 ns.

wordpress shortcode capturesadvanced · All matches and captures9 inputs2.5 µsRuns: 2.39 µs–2.67 µs3.35 µsRuns: 3.17 µs–3.39 µs1.340×Runs: 1.185×–1.415×Favors Ferroni standard API in every run
Scope and evidence

The preg_match_all(/get_shortcode_regex()/s, subject, matches, PREG_SET_ORDER) regex-call shape used by get_post_galleries. The fixed registered tag list here comes from shortcode.php test setup; gallery-specific callback processing is outside the case.

Ratio interval envelope: 1.161×–1.460×.

Time interval envelopes (95% per process). Ferroni standard API: 2.36 µs–2.7 µs. C Oniguruma: 3.13 µs–3.45 µs.

symfony grapheme chunks 1advanced · Grapheme chunks2 inputs1.63 µsRuns: 1.57 µs–1.68 µs2.16 µsRuns: 2.11 µs–2.22 µs1.328×Runs: 1.294×–1.374×Favors Ferroni standard API in every run
Scope and evidence

preg_split($rx, already-NFC subject, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY). Return capture chunks and any unmatched final tail; object cloning and normalization are outside this regex step.

Ratio interval envelope: 1.264×–1.412×.

Time interval envelopes (95% per process). Ferroni standard API: 1.54 µs–1.7 µs. C Oniguruma: 2.08 µs–2.25 µs.

symfony grapheme chunks 2advanced · Grapheme chunks1 inputs420.65 nsRuns: 405.13 ns–421.64 ns586.99 nsRuns: 554.61 ns–599.25 ns1.395×Runs: 1.369×–1.421×Favors Ferroni standard API in every run
Scope and evidence

preg_split($rx, already-NFC subject, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY). Return capture chunks and any unmatched final tail; object cloning and normalization are outside this regex step.

Ratio interval envelope: 1.344×–1.462×.

Time interval envelopes (95% per process). Ferroni standard API: 401.61 ns–427.14 ns. C Oniguruma: 549.62 ns–607.47 ns.

literal firstbasic · First match span1 inputs · controlled probe45.14 nsRuns: 44.88 ns–46.21 ns112.54 nsRuns: 108.22 ns–117.68 ns2.508×Runs: 2.397×–2.547×Favors Ferroni standard API in every run
Scope and evidence

First span of a literal probe on a controlled variant of one genuine Apache test record. No production query distribution is claimed.

Ratio interval envelope: 2.332×–2.637×.

Time interval envelopes (95% per process). Ferroni standard API: 44.24 ns–46.99 ns. C Oniguruma: 106.14 ns–120.03 ns.

literal middlebasic · First match span1 inputs · controlled probe44.43 nsRuns: 41.83 ns–50.24 ns124.97 nsRuns: 106.49 ns–132.42 ns2.981×Runs: 2.120×–2.987×Favors Ferroni standard API in every run
Scope and evidence

First span of a literal probe on a controlled variant of one genuine Apache test record. No production query distribution is claimed.

Ratio interval envelope: 2.088×–3.066×.

Time interval envelopes (95% per process). Ferroni standard API: 41.44 ns–50.65 ns. C Oniguruma: 105.75 ns–133.67 ns.

literal endbasic · First match span1 inputs · controlled probe41.64 nsRuns: 41.49 ns–43.96 ns146.46 nsRuns: 146.38 ns–150.33 ns3.528×Runs: 3.332×–3.610×Favors Ferroni standard API in every run
Scope and evidence

First span of a literal probe on a controlled variant of one genuine Apache test record. No production query distribution is claimed.

Ratio interval envelope: 3.270×–3.692×.

Time interval envelopes (95% per process). Ferroni standard API: 41.14 ns–44.44 ns. C Oniguruma: 144.3 ns–152.18 ns.

literal absentbasic · First match span1 inputs · controlled probe28.32 nsRuns: 28.02 ns–29.03 ns21.03 nsRuns: 20.9 ns–21.59 ns0.751×Runs: 0.720×–0.762×Favors comparator in every run
Scope and evidence

First span of a literal probe on a controlled variant of one genuine Apache test record. No production query distribution is claimed.

Ratio interval envelope: 0.686×–0.781×.

Time interval envelopes (95% per process). Ferroni standard API: 27.75 ns–29.86 ns. C Oniguruma: 20.49 ns–21.84 ns.

linkify http tailadvanced · First match and captures0 inputs———pending
Scope and evidence

Original HTTP/HTTPS/FTP schema validator calls re.lastIndex = pos; re.exec(text), with pos immediately after the schema colon. testSchemaAt first caps the subject at pos+maxLength. This case measures the HTTP link-tail regexp at a known candidate offset on the already-bounded original subject, including //host and path. Schema discovery, normalization, extraction of whole URLs and fuzzy links are outside this boundary.

The ECMAScript /iy pattern contains UTF-16 surrogate branches. A faithful native UTF-8 adapter and output parity are not established.

php cs fixer recursive typesadvanced · First match and captures0 inputs———pending
Scope and evidence

Not timed: Recursive capture state differs after dialect translation. The source parser consumes the nullable capture; whole-match agreement is insufficient.

Recursive capture state differs after dialect translation. The source parser consumes the nullable capture; whole-match agreement is insufficient.

Runner, build and engine profile
Recorded host
macOS-26.3-arm64-arm-64bit-Mach-O; 6 visible CPUs
Measurement started
2026-10-14T07:54:54.866117+00:00
Toolchain
rustc 1.99.0 (b940084d7 2026-09-28) binary: rustc commit-hash: b940084d7eb6a299eb4bfeb8e34901bc051e7ac4 commit-date: 2026-09-28 host: aarch64-apple-darwin release: 1.99.0 LLVM version: 23.1.1
Build
bench, thin LTO, features onigmo; PCRE2_SYS_STATIC=1; bundled source from locked pcre2-sys
Ferroni baseline
Ferroni standard public Regex API; explicit source flags; fallible preflight checks
Comparator
C Oniguruma UTF-8/Oniguruma syntax, -O3; public C API plus output adapters
Budget per lane
3 processes, 30 samples, 1 s warmup, 3 s measurement
Host limits
Dedicated Blacksmith VM; host power/thermal state unavailable. Workflow 38531377256, attempt 1.

Measured revision 1452dce · Blacksmith workflow and job logs · Retained raw evidence and generated tables

Some compile and first-use measurements vary substantially between processes; the displayed ranges include every run. Interval envelopes retain each process's 95% Criterion confidence interval. They are a descriptive screening aid, not a cross-process significance test. Each platform used one VM, so these results do not establish consistency across independent hosts. Host power and thermal state were unavailable. This run did not measure memory.

Patterns with a source

The checked-in catalog records each original pattern, pinned source/hash, flags, dialect recipe, operation, fixture source, oracle runtime, expected byte output and license.

ApplicationMeasured regex work
validator.jsUUID loose shape and v4, IPv4 and IPv6 boolean steps
node-semverSafe FULL version captures and HYPHENRANGE captures
JinjaString, integer and float token match at the lexer candidate offset
DjangoEmail local-part/domain and URL regex steps
Fluent BitApache and NGINX field captures on actual log test records
RailsUnderscore boundary replacement, including zero-width matches
MastodonHashtag regex matches and captures
Python-MarkdownSmart emphasis capture matches at candidate positions
WordPressShortcode matches and all six captures
Symfony StringGrapheme chunk strings on already normalized input

These are isolated regex steps. They do not time the complete validators, parsers, templates, entity extraction or rendering applications. The 513 upstream-derived inputs establish expected semantics, not a production traffic distribution. Four additional literal probes vary match position explicitly on a controlled derivative of a real log record.

Linkify-it's UTF-16 HTTP-tail expression and PHP-CS-Fixer's recursive type grammar remain documented candidates. The former needs a faithful native UTF-8 translation. The latter already exposes a consumed recursive-capture difference; whole-match agreement would not be enough to admit it.

Comparable work before comparable speed

An engine must reproduce every fixture's defined output before timing: boolean, first span, captures with unset groups preserved, all captures, replacement string or grapheme chunks. Missing syntax, semantic differences, execution errors and timeouts have separate statuses. A missing result is not a slow timing or a negative match.

Patterns share one documented dialect recipe. Python strict-end anchors, ECMAScript ASCII digits and PHP ASCII word classes are explicit. Match-at-offset preserves the complete subject when lookbehind needs preceding context. Future alternative solutions to the same task need their own profile and must include necessary preprocessing and output work.

The Ferroni baseline uses the standard public Regex methods, including is_match, find_at, captures_at and captures_iter. A separate Ferroni error-aware API profile uses the fallible *_with searches and iteration with SearchOptions::new(). Both use standard replace_all for replacement because there is no fallible replacement API. Both profiles must pass the source oracles; the baseline also receives a fallible preflight check to expose execution errors.

The two API paths currently manage match state differently: standard searches can reuse the engine's cached workspace, while the parameterized searches create fresh workspace. This can affect reuse costs even with empty search options. Compilation uses the same builder in both profiles. The measured engine implementation is unchanged; these rows expose the API cost separately.

The other comparators are C Oniguruma, pinned Ruby Onigmo, PCRE2 interpreter/JIT, fancy-regex in Oniguruma mode with seek off/on, and Rust regex. Construction includes each profile's contexts and selected JIT. PCRE2's bundled source is pinned through the locked pcre2-sys dependency. Both PCRE2 profiles enable UTF and UCP. Skipping repeated UTF-8 validation for already-valid Rust strings does not disable Unicode character classes. Replacement/output assembly stays within the operation boundary.

regexr 0.6.1 adds separate JIT-off and JIT-enabled profiles. Both use its default SIMD feature and explicit (?u) Unicode mode. Its JIT-enabled selector may retain a non-JIT backend; the selected backend is recorded. Native public Regex methods, capture iteration and literal replacement are measured. Its public API has no context-preserving offset search, so those cases report an API gap. Fallible first-match/capture checks run before timing; native iteration and replacement do not expose every execution error. Each regexr ratio uses the Ferroni baseline from the same additional run.

LLM regex pre-tokenization

The current tiktoken patterns for cl100k_base and o200k_base add a separate application family, inspired by regexr's tokenizer examples. Eleven shared upstream inputs per pattern cover whitespace, contractions, numbers, multilingual text, emoji and the original longer benchmark strings. Every complete match span is checked against Python regex 2026.1.15 using the original expression.

The bounded possessive number quantifier in cl100k_base is written as an equivalent atomic group for the Oniguruma-family dialect. Its other constructs are preserved. Rejections and semantic differences remain visible; acceptance of an older tokenizer example does not establish support for the current pattern.

These timings measure regex pre-tokenization, excluding BPE merges, vocabulary lookups, special tokens and complete tokenizer throughput. They have their own tables and aggregate and do not change the earlier 21-task aggregate.

2 common production cases across 1 families. Pair-specific family-weighted ratio: 2.725×. Controlled probes are excluded from this aggregate. Different supported case pools do not form an engine ranking.

Cases favoring Ferroni's standard API in every run: 2; favoring the comparator: 0; inconclusive: 0.

Ratios are comparator time / Ferroni standard API time. Above 1 favors the standard API; below 1 favors the comparator. Times are medians of 3 process mean estimates; ratios are medians of the ratios paired by repetition. Run ranges retain every process result.

Green factors favor Ferroni, red factors favor the comparator. Inconclusive cases stay neutral even when their point estimate falls above or below 1.

On narrow screens, scroll the table horizontally to compare timings and interpretation.

macos-arm64 · Steady reuse · complete fixture batch · Ferroni standard API and C Oniguruma
WorkloadFerroni standard APIRustC OnigurumaCFerroni relative throughputInterpretation and scope
tiktoken cl100k baseadvanced · All match spans11 inputs99.2 µsRuns: 88.93 µs–101.18 µs260.42 µsRuns: 253.58 µs–279.21 µs2.625×Runs: 2.506×–3.140×Favors Ferroni standard API in every run
Scope and evidence

Non-overlapping UTF-8 spans from tiktoken cl100k_base regex pre-tokenization; excludes BPE merges, vocabulary lookup, special-token handling and whole-model tokenization.

Ratio interval envelope: 2.443×–3.185×.

Time interval envelopes (95% per process). Ferroni standard API: 88.56 µs–102.23 µs. C Oniguruma: 249.7 µs–282.05 µs.

tiktoken o200k baseadvanced · All match spans11 inputs91.4 µsRuns: 89.98 µs–93.19 µs261.03 µsRuns: 258.48 µs–263.4 µs2.828×Runs: 2.826×–2.901×Favors Ferroni standard API in every run
Scope and evidence

Non-overlapping UTF-8 spans from tiktoken o200k_base regex pre-tokenization; excludes BPE merges, vocabulary lookup, special-token handling and whole-model tokenization.

Ratio interval envelope: 2.750×–2.965×.

Time interval envelopes (95% per process). Ferroni standard API: 88.93 µs–94.31 µs. C Oniguruma: 254.57 µs–266.17 µs.

Runner, build and engine profile
Recorded host
macOS-26.3-arm64-arm-64bit-Mach-O; 6 visible CPUs
Measurement started
2026-10-14T14:02:20.096839+00:00
Toolchain
rustc 1.99.0 (b940084d7 2026-09-28) binary: rustc commit-hash: b940084d7eb6a299eb4bfeb8e34901bc051e7ac4 commit-date: 2026-09-28 host: aarch64-apple-darwin release: 1.99.0 LLVM version: 23.1.1
Build
bench, thin LTO, features onigmo; PCRE2_SYS_STATIC=1; bundled source from locked pcre2-sys
Ferroni baseline
Ferroni standard public Regex API; explicit source flags; fallible preflight checks
Comparator
C Oniguruma UTF-8/Oniguruma syntax, -O3; public C API plus output adapters
Budget per lane
3 processes, 30 samples, 1 s warmup, 3 s measurement
Host limits
Dedicated Blacksmith VM; host power/thermal state unavailable. Workflow 38571723690, attempt 1. Separate LLM pre-tokenization corpus.

Measured revision 7b9933c · Blacksmith workflow and job logs · Retained raw evidence and generated tables

Eligibility describes these configurations. Rust regex, for example, disables octal escapes by default and offers RegexBuilder::octal(true); its rejection of Django's email local-part expression in this profile is a dialect-option difference. Other configurations or task-equivalent expressions need separate, named profiles with the same checked output contract.

A different result is not necessarily a missing feature or a library defect. For example, regexr joins an Indic consonant cluster that the retained Symfony grapheme oracle separates. Its implementation includes the Unicode GB9c grapheme rule. The feature matrix still shows grapheme support; the workload requires the specific retained output, so that result cannot enter its timing comparison.

Measurement and interpretation

Compile, compile-plus-first-use and steady reuse are separate lanes. First use concerns the first fixture, while steady reuse executes the complete fixture batch. These are fresh-pattern costs in a warmed process; they do not measure OS-process startup. Memory, where selected, is whole-process peak RSS including runtime, catalog, allocator and output, rather than isolated pattern storage.

The runner starts with three independent processes per pair, 30 Criterion samples per lane and three seconds measurement. It rotates/reverses engine order, keeps raw samples and intervals, and reports inconsistent differences as inconclusive. Short diagnostic runs cannot establish publication claims. Review host conditions and stability before interpreting a publication candidate.

Each comparison lists its exact common tasks. Families carry equal aggregate weight regardless of fixture or variant count. Aggregates with different supported task pools do not establish an engine ranking. ARM and x86 results are reported separately.

The aggregate takes the geometric mean of the median case ratios within each family, then the geometric mean across families. It describes this selected corpus rather than a production traffic mix.

The corpus guide contains runnable commands, case selection, measured boundaries, current coverage limits and the requirements for adding cases.