What most people fail to consider when approaching questions like this is what happens when the regex is unable to find a match. That's when the killer performance sinkholes are most likely to appear. For example, take Tim's example, where you're looking for something like <tag>Hello!
. Consider what happens with:
<.*?>Hello!
The regex engine finds a <
and it quickly finds a closing >
, but not >Hello!
. So the .*?
continues looking for a >
that is followed by Hello!
. If there isn't one, it will go all the way to the end of the document before it gives up. Then the regex engine resumes scanning until it finds another <
, and tries again. We already know how that's going to turn out, but the regex engine, typically, doesn't; it goes through the same rigamarole with every <
in the document. Now consider the other regex:
<[^>]*>Hello!
As before, it quickly matches from the <
to the >
, but fails to match Hello!
. It will backtrack to the <
, then quit and start scanning for another <
. It will still check every <
like the first regex did, but it won't search all the way to the end of the document every time it finds one.
But it's even worse than that. If you think about it, .*?
is effectively equivalent to a negative lookahead. It's saying "Before consuming the next character, make sure the remainder of the regex can't match at this position." In other words,
/<.*?>Hello!/
...is equivalent to:
/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/
So at every position you're performing, not just a normal match attempt, but a much more expensive lookahead. (It's at least twice as costly, because the lookahead has to scan at least one character, then the .
goes ahead and consumes a character.)
((*FAIL)
is one of Perl's backtracking-control verbs (also supported in PHP). |\z(*FAIL)
means "or reach the end of the document and give up".)
Finally, there's another advantage of the negated-character-class approach. While it doesn't (as @Bart pointed out) act like the quantifier is possessive, there's nothing to stop you from making it possessive, if your flavor supports it:
/<[^>]*+>Hello!/
...or wrap it in an atomic group:
/(?><[^>]*>)Hello!/
Not only will these regexes never backtrack unnecessarily, they don't have to save the state information that makes backtracking possible.
[^>]*
will only not backtrack if it is followed by what is inside the negated class ([^>]*>
in this case). Kobi, I know you know and probably meant this, but wanted to make sure others don't think that[^>]*
and[^>]*+
(possessive) are the same. Besides that, nice answer! – Hauge