A common misconception about regular expression performance is that lazy quantifiers (also called non-greedy, reluctant, minimal, or ungreedy) are faster than their greedy equivalents. That's generally not true, but with an important qualifier: in practice, lazy quantifiers often are faster. This seeming contradiction is due to the fact that many people rely on backtracking to compensate for the impreciseness of their patterns, often without realizing this is what they're doing. Hence, the previously mentioned misconception stems from that fact that lazy quantifiers often result in much less backtracking when combined with long subject strings and the (mis)use of overly flexible regex tokens such as the dot.
Consider the following simple example: When the regexes <.*?>
and <[^>]*>
are applied to the subject string "<0123456789>", they are functionally equivalent. The only difference is how the regex engine goes about generating the match. However, the latter regex (which uses a greedy quantifier) is more efficient, because it describes what the user really means: match the character "<", followed by any number of characters which are not ">", and finally, match the character ">". Defined this way, it requires no backtracking in the case of any successful match, and only one backtracking step in the case of any unsuccessful match. Hand-optimization of regex patterns largely revolves around the ideas of reducing backtracking and the steps which are potentially required to match or fail at any given character position, and here we've reduced both cases to the absolute minimum.
So what exactly is different about how a backtracking, leftmost-first regex engine (your traditional NFA engine type, which describes most modern, Perl-derivative flavors) goes about matching our subject text when working with these two regexes? Let's first take a look at what happens with <.*?>
. I'll use the RegexBuddy debugger style to illustrate each step.
Note: If you're reading this in a feed reader or aggregator which strips out inline styles, see the original post, which should make things much easier to follow.
- Beginning match attempt at character 0
- <
- <ok
- <backtrack
- <0
- <0backtrack
- <01
- <01backtrack
- <012
- <012backtrack
- <0123
- <0123backtrack
- <01234
- <01234backtrack
- <012345
- <012345backtrack
- <0123456
- <0123456backtrack
- <01234567
- <01234567backtrack
- <012345678
- <012345678backtrack
- <0123456789
- <0123456789>
- Match found
The text ok means the engine completed a successful, zero-length match, and backtrack shows where the engine backtracks to the last choice point, which it does after trying and failing to match the next token (>
). As you can see here, with lazy quantification the engine backtracks forward after each character which is not followed by ">".
Here's what happens with <[^>]*>
:
- Beginning match attempt at character 0
- <
- <0123456789
- <0123456789>
- Match found
As you can see, 20 fewer steps are required, and there is no backtracking. This is faster, and can also increase the potential for internal optimization of the matching process. As a simple example, when greedily quantifying an any-character token, the engine can simply move the read-head to the end of the string, rather than testing every character along the way (this is an oversimplification of the process taken by modern regex engines, but you get the idea). You can see this exact kind of internal optimization occurring in IE with the "trim8" pattern in my Faster JavaScript Trim post from a while back.
You might be wondering what happens if we use the regex <.*>
. Take a look:
- Beginning match attempt at character 0
- <
- <0123456789>
- <0123456789>backtrack
- <0123456789
- <0123456789>
- Match found
Although the output didn't change much when used with our subject string of "<0123456789>", you can see that the .*
caused the engine to trek all the way to the end of the string, then backtrack from the right. If we'd used a 10,000-character string without newlines or other ">" characters, that would've resulted in nearly 10,000 backtracks.
In short:
- Make your patterns more explicit where it makes sense, to better guide the regex engine and avoid backtracking.
- Be careful about using the more appropriate type of quantification (to your best approximation, since this often depends on the subject string) even when it has no impact on the text matched by your regex.
- If possible, avoid greedy quantification of the dot and other needlessly flexible tokens.