Skip to content

<regex>: Accelerate greedy wildcard loops - #6475

Open
Julian Müller (muellerj3) wants to merge 1 commit into
microsoft:mainfrom
muellerj2:regex-accelerate-greedy-dot-loops
Open

Julian Müller (muellerj3) wants to merge 1 commit into
microsoft:mainfrom
muellerj2:regex-accelerate-greedy-dot-loops

Conversation

@muellerj3

@muellerj3 Julian Müller (muellerj3) commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Towards #5971. This speeds up .* and similar greedy wildcard loops, delegating the search for the first non-matching character to std::find() or std::find_first_of() to enable vectorization when available.

While the speedups are impressive, I have to point out that we fundamentally can't beat Boost.Regex in its default mode on very long inputs because Boost.Regex makes . match any character, including newlines or nulls. In Boost.Regex, spec-conformant behavior must be turned on explicitly.

For non-random-access iterators, the new code might iterate the input up to four times now instead of once (three of which are hidden in std::distance() and std::advance() calls), so the change can potentially degrade performance for such iterators if iterating is expensive. If it turns out to be a practical problem, we can disable this optimization for such iterators or add optimized code for non-random-access iterators.

Strictly speaking, we lack unit test coverage in _Find_next_ecmascript_line_terminator() for the bool branch. But this case is very esoteric and the correctness of the code is easy to verify, so I think we can live with this.

Benchmark

regex_match
benchmark before [ns] after [ns] speedup
bm_match_sequence_of_as/"a*"/100 571.987 544.085 1.05
bm_match_sequence_of_as/"a*"/200 1000.98 983.538 1.02
bm_match_sequence_of_as/"a*"/400 1799.67 1799.67 1.00
bm_match_sequence_of_as/"a*" posix longest/100 1464.84 1349.75 1.09
bm_match_sequence_of_as/"a*" posix longest/200 2825.05 2490.24 1.13
bm_match_sequence_of_as/"a*" posix longest/400 5312.5 5156.25 1.03
bm_match_sequence_of_as/"a*?"/100 2050.78 1883.37 1.09
bm_match_sequence_of_as/"a*?"/200 3923.69 3609.79 1.09
bm_match_sequence_of_as/"a*?"/400 7672.99 6975.45 1.10
bm_match_sequence_of_as/"(?:a)*"/100 571.987 530.134 1.08
bm_match_sequence_of_as/"(?:a)*"/200 976.562 941.685 1.04
bm_match_sequence_of_as/"(?:a)*"/400 1925.22 1803.15 1.07
bm_match_sequence_of_as/"(a)*"/100 2727.4 2668.11 1.02
bm_match_sequence_of_as/"(a)*"/200 5580.36 5156.25 1.08
bm_match_sequence_of_as/"(a)*"/400 10742.2 10253.9 1.05
bm_match_sequence_of_as/"(a)*?"/100 3376.12 3149.42 1.07
bm_match_sequence_of_as/"(a)*?"/200 6556.92 6277.9 1.04
bm_match_sequence_of_as/"(a)*?"/400 13183.5 12276.8 1.07
bm_match_sequence_of_as/"(?:b|a)*"/100 3609.79 3442.39 1.05
bm_match_sequence_of_as/"(?:b|a)*"/200 7254.46 6975.45 1.04
bm_match_sequence_of_as/"(?:b|a)*"/400 14753 13497.4 1.09
bm_match_sequence_of_as/"(b|a)*"/100 6696.43 6093.75 1.10
bm_match_sequence_of_as/"(b|a)*"/200 14299.7 12834.8 1.11
bm_match_sequence_of_as/"(b|a)*"/400 29994.4 31062 0.97
bm_match_sequence_of_as/"(a)(?:b|a)*"/100 3515.62 3442.39 1.02
bm_match_sequence_of_as/"(a)(?:b|a)*"/200 7254.46 6801.06 1.07
bm_match_sequence_of_as/"(a)(?:b|a)*"/400 13950.9 14125.2 0.99
bm_match_sequence_of_as/"(a)(b|a)*"/100 6417.41 6093.75 1.05
bm_match_sequence_of_as/"(a)(b|a)*"/200 14125.2 12869.6 1.10
bm_match_sequence_of_as/"(a)(b|a)*"/400 30482.6 28250.4 1.08
bm_match_sequence_of_as/"(a)(?:b|a)*c"/100 4450.33 4425.92 1.01
bm_match_sequence_of_as/"(a)(?:b|a)*c"/200 9416.81 8789.02 1.07
bm_match_sequence_of_as/"(a)(?:b|a)*c"/400 17996.8 17578.3 1.02
bm_match_sequence_of_as/".*" ecma/1000 8370.5 219.727 38.09
bm_match_sequence_of_as/".*" ecma/10000 89979.2 941.685 95.55
bm_match_sequence_of_as/".*" ecma/100000 878514 8196.15 107.19
bm_match_sequence_of_as/".*" posix any/1000 9416.81 141.253 66.67
bm_match_sequence_of_as/".*" posix any/10000 94164.3 392.369 239.99
bm_match_sequence_of_as/".*" posix any/100000 962182 2668.11 360.62
bm_match_sequence_of_as/".*" posix longest/100 1764.79 920.759 1.92
bm_match_sequence_of_as/".*" posix longest/200 3442.39 1688.06 2.04
bm_match_sequence_of_as/".*" posix longest/400 6417.41 3295.9 1.95
bm_match_sequence_of_9a1b/"(?:a*b)*"/100 8579.76 8370.5 1.02
bm_match_sequence_of_9a1b/"(?:a*b)*"/200 16497 16113.4 1.02
bm_match_sequence_of_9a1b/"(?:a*b)*"/400 36900.6 34494 1.07

@muellerj3
Julian Müller (muellerj3) requested a review from a team as a code owner October 3, 2026 15:42
Copilot AI balanced review requested due to automatic review settings October 3, 2026 15:42
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The optimization preserves existing matching semantics and is supported by focused regression tests and benchmarks.

Review effort: Balanced
Findings: None

What changed in this PR

Accelerates greedy wildcard regex repetitions by using optimized search algorithms while preserving ECMAScript and POSIX semantics.

Changes:

  • Adds optimized handling for greedy dot repetitions.
  • Reuses line-terminator search logic.
  • Adds correctness tests and performance benchmarks.
File Description
stl/​inc/​regex Implements optimized wildcard-loop matching.
tests/​std/​tests/​VSO_0000000_regex_use/​test.cpp Tests wildcard bounds and character semantics.
tests/​std/​tests/​GH_000995_regex_custom_char_types/​test.cpp Tests custom character types.
benchmarks/​src/​regex_match.cpp Benchmarks ECMAScript and POSIX wildcard loops.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@AlexGuteniev

Copy link
Copy Markdown
Contributor

ARM64 numbers could be even more impressive, as #6468 is merged

Comment thread stl/inc/regex
_Cur = _STD _Find_next_ecmascript_line_terminator<_Elem>(_Cur, _Last);
break;
case _Fl_rep_dot_posix:
_Cur = _STD _Find_unchecked(_Cur, _Last, _Elem());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could standardize on _Elem{}.

Comment thread stl/inc/regex
if constexpr (sizeof(_Elem) == 1) {
static constexpr _Elem _Line_terminators_size_1[] = {
static_cast<_Elem>(_Meta_cr), static_cast<_Elem>(_Meta_nl)};
return _STD find_first_of(_First, _Last, _Line_terminators_size_1, _STD end(_Line_terminators_size_1));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this case is very important we could have specialized vectorized find_first_of with hardcoded needle, it could use the best strategy. I don't expect that to be dramatically faster, but still likely there's room for improvement.

@StephanTLavavej Stephan T. Lavavej (StephanTLavavej) added performance Must go faster regex meow is a substring of homeowner labels Oct 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Must go faster regex meow is a substring of homeowner

Projects

Status: Initial Review

Development

Successfully merging this pull request may close these issues.

4 participants