Repository navigation
Regex pattern '^' is slower than manually lowered equivalent #125277
Description
Activity
- addeduntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner
on Mar 6, 2026 dotnet-policy-service commented
on Mar 6, 2026 ContributorMore actionsTagging subscribers to this area: @dotnet/area-system-text-regularexpressions
See info in area-owners.md if you want to be subscribed.Turns out
^is common! Updated above.When ^ is the only interesting construct (e.g., bare ^ with Multiline), the engine checks every position for "preceded by \n or start of string".
Are you sure about this? When I use ^ with Multiline, I get this:
private bool TryFindNextPossibleStartingPosition(ReadOnlySpan<char> inputSpan) { int pos = base.runtextpos; // The pattern has a leading beginning-of-line anchor. if (pos > 0 && inputSpan[pos - 1] != '\n') { int newlinePos = inputSpan.Slice(pos).IndexOf('\n'); if ((uint)newlinePos > inputSpan.Length - pos - 1) { goto NoMatchFound; } pos += newlinePos + 1; if (pos > inputSpan.Length) { goto NoMatchFound; } } return true; // No match found. NoMatchFound: base.runtextpos = inputSpan.Length; return false; }
which is indeed using IndexOf. In contrast if I use
(?<=\n|\A), it doesn't even generate a TryFindNextPossibleStartingPosition and instead ends up effectively trying to match at every position.I trusted the perf test + results. Let me see.
Ugh. It's a bug in the generated code I pasted above. base.runtextpos isn't being set when it should be.
I came to the same conclusion and was working on a fix (well AI was). I guess we can compare.
Anyway, pasting in more detailed perf numbers since I did get them.
1000 matches, varying inter-match gap, using
Regex.Count()withRegexOptions.Compiled:Gap (chars) ^ms(?=\n)ms^ns/match(?=\n)ns/match^is slower by5 0.030 0.021 30 21 1.4x 20 0.092 0.017 92 17 5x 80 0.341 0.020 341 20 17x 320 2.008 0.022 2,008 22 91x 1,280 16.805 0.047 16,805 47 358x Worth a microbenchmark? I assume not as it's "just a bug" and we keep those focused. But, it's apparently a common pattern, so we care about it.
Worth a microbenchmark?
I don't think so.
Reacted by Dan MoseleyI see there are no RegexOptions.Multiline microbenchmarks at all. That might be something worth addressing there.
fixed in #125280
- removeduntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner
on Mar 7, 2026 - locked and limited conversation to collaborators
on Apr 6, 2026
(I thought this was only a curiosity but it turns out patterns like
^are common in the real world corpus!)When
^is the only interesting construct (e.g., bare^withMultiline), the engine checks every position for "preceded by\nor start of string". The semantically equivalent hand-lowered lookbehind(?<=\n|\A)is ~40% faster on this case, likely becauseFindFirstCharcan use vectorizedIndexOf('\n')to jump to candidate positions.In practice, most patterns have content after
^(e.g.,^\w+,^Line\d+) that givesFindFirstChara better skip target, which largely masks the Bol overhead. The realistic patterns below show only a ~10% gap or none at all. However, the real-world regex patterns dataset has several multiline^patterns that could be affected:^^with Multiline -- fully affected^ +$^+ spaces -- weak skip^ {4}^+ spaces -- weak skip^ *> ?^+ optional spaces -- weak skip^(\d+)\.(\d+)\.(\d+)^+ digit -- depends on whetherFindFirstCharcan use\d^\s*GO\s*$^+ whitespace -- weak skip^(?!$)^+ lookahead only -- no skip targetPatterns with a literal after
^(e.g.,^#includeat 2,606 packages,^-+BEGINat 1,964) are largely unaffected sinceFindFirstCharuses the literal to skip.Results (.NET 11.0.0-preview.1, BenchmarkDotNet, 30 iterations, InProcess, stock runtime,
\ntext with 1000 lines of ~40 chars each):^(?<=\n|\A)^(bare)^\w+FindFirstCharuses\wto skip^.+$^Line\d+For comparison,
$(Eol) does not show this gap -- native$is already as fast as(?=\n\|\z):$(?=\n|\z)$on\ntext$on\r\ntextThis suggests
$already has an efficient scan optimization that^lacks.Discovered while investigating AnyNewLine anchor lowering performance (#124701).
Repro code (BenchmarkDotNet, .NET 11)