Performance¶
One line: Regex performance is three separate questions — can this pattern explode, how much does it do per character, and how often is it compiled — and this library records answers to the first two as step counts and cliffs rather than timings, because a timing is not a fact about the pattern.
Level: 301 · a pattern is slow, or you are choosing one for a hot path
Why there are no timings here¶
A key in this library must be byte-identical on the author's Mac and on the Ubuntu CI runner. A millisecond is not. So the measured pages record what can be recorded: whether an engine answered within a fixed cap, how many characters a Java CharSequence was asked for, how many match steps PCRE2 needed before its limit tripped. Those are properties of the pattern and the engine, and they reproduce.
Question 1 — can it explode?¶
The only performance question that matters until it is answered. A backtracking engine with a nested quantifier over overlapping alternatives — (a+)+, (\w+\s?)*, most hand-written email validators — can take exponential time on a subject that fails, and the subject is chosen by whoever sends the input.
- The shape, and which engines never answer, answer until the input grows, or cannot be made to explode: Catastrophic backtracking.
- The construct that cuts the search dead, and what it does to the match: Atomic groups and possessive quantifiers.
- The theory — why a backreference forces backtracking at all: What a backreference costs.
- The engine choice that makes the question go away: Go's
regexpand Rust'sregexare linear in the subject by construction. Choosing an engine.
Question 2 — how much per character?¶
Once a pattern cannot explode, it can still do more work than it needs to, and the differences are constant factors that add up on a large input:
| Do | Instead of | Because |
|---|---|---|
[^"]* |
.*? |
the class stops on its own; the lazy quantifier retries the tail at every step — measured in read counts |
anchor: ^… or \A… |
an unanchored pattern that must start at 0 | without the anchor the engine tries every offset |
| a literal prefix first | \w+@… |
engines search for literal prefixes with fast string search before running the automaton |
(?:…) |
(…) |
a capture costs bookkeeping on every backtrack; only capture what code reads |
| longest alternative first | \d+|\d+.\d+ |
in a leftmost-first engine the order decides both the answer and how many attempts are made — alternation |
| a possessive or atomic group | a greedy one that can never usefully give back | but only where nothing after can overlap — possessive |
| a specific class | . |
. has to be told at every position that this is not a newline |
None of these is worth doing until question 1 is answered, and none of them is worth doing at all on a pattern that runs once.
Question 3 — how often is it compiled?¶
Compilation is the expensive step in every engine: parsing, building the automaton or the backtracking program, the Unicode tables. A pattern compiled inside a loop is compiled once per iteration.
| Language | Compile once | The trap |
|---|---|---|
| Python | re.compile at module level |
re.match(p, s) in a loop is fine — re caches the last 512 patterns — until the loop uses more than 512 |
| Perl | qr// once; a constant // is compiled with the program |
/$var/ recompiles when $var changes; add /o only if it never does |
| JavaScript | a literal /…/ at module level |
new RegExp(s) in a loop compiles every time |
| Ruby | a literal at module level, or a constant | Regexp.new in a loop |
| Java | static final Pattern |
String.matches(regex) compiles every call |
| Go | regexp.MustCompile at package level |
regexp.MatchString compiles every call |
| Rust | LazyLock<Regex> or OnceLock |
Regex::new in a loop — and Clippy warns |
Keywords on this page¶
Possessive quantifier · Atomic group · Lazy quantifier · Alternation · Non-capturing group · Backtracking verbs · (*LIMIT_MATCH).
See also¶
- Backtracking — the chapter.
- Russ Cox, Regular Expression Matching Can Be Simple And Fast ↗ — why the automata engines are built the way they are.