regex-bench

March 24, 2026 · View on GitHub

Cross-language regex benchmark for real-world patterns.

Created to provide data for golang/go#26623 discussion on Go regex performance.

Test Environment

All benchmarks run on identical conditions:

  • OS: Linux (Ubuntu via WSL2 or GitHub Actions)
  • Input: 6.0 MB generated text file
  • Method: Each engine compiled natively, same input file, same patterns

Note: Cross-compiled Go binaries run in WSL2 for fair comparison with Rust.

Results (v0.12.18)

GitHub Actions Ubuntu (AMD EPYC), 6.0 MB input (using FindAll for fair comparison)

PatternGo stdlibGo coregexRust regexvs stdlibvs RustWinner
inner_literal231 ms0.29 ms0.29 ms797x~parityparity
email261 ms0.54 ms0.31 ms482x1.7x slowerRust
uri262 ms0.84 ms0.35 ms311x2.4x slowerRust
ip498 ms2.10 ms12.0 ms237x5.6x fastercoregex
multiline_php103 ms0.66 ms0.66 ms156x~parityparity
suffix234 ms1.83 ms1.07 ms128x1.7x slowerRust
literal_alt475 ms4.35 ms0.69 ms109x6.3x slowerRust
multi_literal1391 ms12.64 ms4.72 ms110x2.6x slowerRust
version169 ms1.81 ms0.70 ms93x2.5x slowerRust
http_methods107 ms1.28 ms0.58 ms83x2.2x slowerRust
char_class554 ms47.95 ms50.07 ms11x1.0x fastercoregex
alpha_digit277 ms25.83 ms12.27 ms10x2.1x slowerRust
word_digit280 ms26.22 ms11.97 ms10x2.1x slowerRust
word_repeat641 ms185 ms49 ms3x3.7x slowerRust
anchored0.01 ms0.03 ms0.01 ms~1x3.0x slower
anchored_php0.00 ms0.01 ms0.01 ms~1x~same

coregex v0.12.18 — Flat DFA transition table, integrated prefilter skip-ahead in DFA+PikeVM, 4x loop unrolling. 35% faster than v0.12.14 baseline. Run make extreme for 2500x demo.

Key Findings

Go coregex v0.12.18 vs Go stdlib:

  • All patterns: 3-797x faster
  • Best: inner_literal 797x, email 482x, uri 311x
  • ip 237x (DigitPrefilter)
  • multiline_php 156x (MultilineReverseSuffix, Rust parity!)
  • suffix 128x, literal_alt 109x, multi_literal 110x
  • http_methods 83x (UseTeddy with lineAnchorWrapper)
  • char_class 11x (CharClassSearcher, faster than Rust!)
  • word_repeat 3x (flat DFA with 4x unrolling)

Go coregex faster than or at parity with Rust (4 patterns):

  • ip: coregex 5.6x faster (2.1ms vs 12.0ms)
  • char_class: coregex 1.0x faster (48ms vs 50ms)
  • inner_literal: ~parity (0.29ms vs 0.29ms)
  • multiline_php: ~parity (0.66ms vs 0.66ms)

Rust faster than coregex:

  • literal_alt: Rust 6.3x faster (Teddy with more buckets)
  • word_repeat: Rust 3.7x faster (DFA state acceleration)
  • multi_literal: Rust 2.6x faster
  • version: Rust 2.5x faster
  • uri: Rust 2.4x slower
  • http_methods: Rust 2.2x faster
  • alpha_digit, word_digit: Rust 2.1x faster
  • email: Rust 1.7x faster
  • suffix: Rust 1.7x faster

Note: Rust regex has 10+ years of development. coregex optimizations are targeted, not universal.

Analysis

EngineStrengthsWeaknesses
Go stdlibSimple, no dependenciesNo optimizations, 3.6-926x slower
Go coregexFlat DFA, reverse search, SIMD prefilters, Aho-Corasick, bidirectional DFA, 4 patterns at Rust parity or fasterTeddy Go/ASM gap, word_repeat
Rust regexDFA state acceleration, Teddy Fat, mature DFAip, char_class slower than coregex

v0.12.18 (Current):

  • Flat DFA transition table (Rust approach) — single flat array, no pointer chase
  • Integrated prefilter skip-ahead in DFA loop (Rust hybrid/search.rs approach)
  • PikeVM integrated prefilter skip-ahead (Rust pikevm.rs approach)
  • 4x loop unrolling in DFA hot loop
  • 4 patterns at Rust parity or faster: ip (5.6x), char_class (1.0x), inner_literal (parity), multiline_php (parity)
  • 35% faster than v0.12.14 baseline on Kostya's LangArena benchmark

Historical Improvements:

  • v0.12.18: Flat DFA transition table, integrated prefilter, 4x unrolling — 3x from Rust
  • v0.12.17: Fix LogParser ARM64 regression, restore DFA/Teddy for (?m)^
  • v0.12.16: WrapLineAnchor for (?m)^ patterns
  • v0.12.15: Per-goroutine DFA cache, 7 correctness fixes, stdlib compat test (38/38)
  • v0.12.14: Concurrent isMatchDFA safety fix (#137)
  • v0.12.13: FatTeddy AVX2 fix, prefilter acceleration, AC v0.2.1
  • v0.12.1: Bidirectional DFA fallback, bounded repetitions fix (#115), AVX2 Teddy fix (#74)
  • v0.12.0: Anti-quadratic guard, DFA loop unrolling, DFA cache clear & continue
  • v0.11.4: FindAll multiline fix, 78x faster (Issue #102)
  • v0.11.3: UseMultilineReverseSuffix prefix fast path 319-552x (Issue #99)
  • v0.11.1: UseMultilineReverseSuffix for multiline patterns (Issue #97)
  • v0.11.0: UseAnchoredLiteral 32-133x speedup (Issue #79)
  • v0.10.10: ReverseSuffix CharClass Plus fix
  • v0.10.9: UTF-8 optimization + fuzz-found bug fixes
  • v0.10.8: FindAll allocation fix for anchored patterns
  • v0.10.7: UTF-8 fixes + 100% stdlib API compatibility
  • v0.10.5: CompositeSearcher backtracking fix
  • v0.10.0: Fat Teddy AVX2 (33-64 patterns, 9+ GB/s)
  • v0.9.5: Aho-Corasick integration, Teddy 32 patterns

Extreme Speedups (1000-3000x)

The "3-3000x faster" claim refers to specific edge cases where coregex prefilters can skip entire input:

make extreme       # Run on no-match data (~300-560x)
make extreme-3000x # Run on no-digits data (1000-3000x)

GitHub Actions Ubuntu results (6 MB no-digits data, v0.12.1):

PatternGo stdlibGo coregexSpeedup
ip_nomatch422 ms166 µs2542x
suffix_find245 ms126 µs1945x
phone_nomatch143 ms166 µs863x
inner_nomatch229 ms382 µs598x

Extreme Benchmark

Note: Results vary between runs (±30%) due to CI VM load and OS scheduling. The key insight: coregex operates in microseconds, stdlib in hundreds of milliseconds.

When do we see 3000x?

The 3000x speedup occurs in coregex's own benchmark suite (go test -bench) under specific conditions:

  • Pattern: IP regex on data with NO IP addresses
  • Size: 1 MB of pure text
  • Measurement: go test -bench with multiple iterations
// In coregex repo:
BenchmarkIPRegex_Find/stdlib_1MB_no_ips    74.5ms
BenchmarkIPRegex_Find/coregex_1MB_no_ips   22.4µs  // 3324x

The extreme speedup happens because:

  1. DigitPrefilter scans for first digit character
  2. No digits in input → entire 1 MB skipped in ~20µs
  3. stdlib must scan byte-by-byte → 74ms

Verified speedups (from coregex repo, docs/dev/SPEEDUP_VERIFICATION.md):

PatternStrategyMax Speedup
IP no-match (1MB)DigitPrefilter3324x
.*\.txt$ (1MB)ReverseSuffix1124x
.*error.* (32KB)ReverseInner909x

The speedup depends on input characteristics. Real-world mixed data shows 15-560x.

Patterns Tested

NamePatternTypeOptimization
literal_alterror|warning|fatal|critical4-literal alternationTeddy SIMD
multi_literalapple|banana|...|orange12-literal alternationAho-Corasick
anchored^HTTP/[12]\.[01]Start anchor
inner_literal.*@example\.comInner literalReverse search
suffix.*\.(txt|log|md)Suffix matchReverse search
char_class[\w]+Character classCharClassSearcher
email[\w.+-]+@[\w.-]+\.[\w.-]+Complex real-worldMemmem SIMD
uri[\w]+://[^/\s?#]+[^\s?#]+...URL with query/fragmentMemmem SIMD
version\d+\.\d+\.\d+Version numbersDigitPrefilter
ip(?:(?:25[0-5]|2[0-4][0-9]|...)\.){3}...IPv4 validationDigitPrefilter + LazyDFA
http_methods(?m)^(GET|POST|PUT|DELETE|PATCH)Multiline log parsingBranchDispatch
anchored_php^/.*[\w-]+\.phpURL path matchingUseAnchoredLiteral
multiline_php(?m)^/.*\.phpMultiline PHP pathsUseMultilineReverseSuffix
word_repeat(\w{2,8})+Word quantifiersBoundedBacktracker + DFA fallback

Running Benchmarks

# Generate input data (6 MB)
go run scripts/generate-input.go

# Build for Linux
cd go-stdlib && GOOS=linux GOARCH=amd64 go build -o ../bin/go-stdlib-linux . && cd ..
cd go-coregex && GOOS=linux GOARCH=amd64 go build -o ../bin/go-coregex-linux . && cd ..

# Run all in WSL/Linux for fair comparison
wsl ./bin/go-stdlib-linux input/data.txt
wsl ./bin/go-coregex-linux input/data.txt
wsl ./bin/rust-benchmark input/data.txt

CI Benchmarks

Benchmarks run automatically on GitHub Actions (Ubuntu) for reproducible results.

Benchmark

Auto-generated comparison table in Job Summary:

  • Side-by-side results for all 3 engines
  • Speedup calculations (vs stdlib, vs Rust)
  • Winner column with bold formatting
  • Raw output in collapsible section

License

MIT