Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate capturing from normal states in NonBacktracking #65340

Merged

Conversation

olsaarik
Copy link
Contributor

@olsaarik olsaarik commented Feb 14, 2022

This should address issue #65289 where a long literal string pattern caused an out-of-memory error. The capturing support added in PR #65129 effectively doubled memory usage by adding a second capturing-enabled transition array that grew in lockstep with the original one. This PR splits capturing states into a separately indexed set of states, which allows the two transition arrays to grow separately.

Edit: that branch name should say nonbacktracking.

@ghost
Copy link

ghost commented Feb 14, 2022

Tagging subscribers to this area: @dotnet/area-system-text-regularexpressions
See info in area-owners.md if you want to be subscribed.

Issue Details

This should address issue #65289 where a long literal string pattern caused an out-of-memory error. The capturing support added in PR #65129 effectively doubled memory usage by adding a second capturing-enabled transition array that grew in lockstep with the original one. This PR splits capturing states into a separately indexed set of states, which allows the two transition arrays to grow separately.

Author: olsaarik
Assignees: olsaarik
Labels:

area-System.Text.RegularExpressions

Milestone: -

@olsaarik
Copy link
Contributor Author

@stephentoub Is there a way to run the System.Text.RegularExpressions.Tests.RegexMatchTests.Match_VaryingLengthStrings_Huge test on the CI hardware to make sure this actually addresses the issue?

@joperezr
Copy link
Member

joperezr commented Feb 14, 2022

Yes there is. I can trigger that, just let me check real quick what the right one is.

@joperezr
Copy link
Member

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dotnet dotnet deleted a comment from azure-pipelines bot Feb 14, 2022
@olsaarik
Copy link
Contributor Author

Cool, thank you Jose!

@olsaarik
Copy link
Contributor Author

Looks like the outerloop tests failed on the issue fixed by #65333 . I didn't see out of memory errors. I'll rebase onto main and re-run the outerloop tests though.

This reduces memory usage.
@olsaarik olsaarik force-pushed the fix-backtracking-long-pattern-memory-usage branch from c5499ca to 0872730 Compare February 15, 2022 18:13
@olsaarik
Copy link
Contributor Author

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

{
int newsize = _statearray.Length + 1024;
Array.Resize(ref _statearray, newsize);
Array.Resize(ref _delta, newsize << _mintermsCount);
Copy link
Member

Choose a reason for hiding this comment

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

I was going to suggest we use ref locals to avoid the duplication between the if/else branches, but the fact that _delta and _capturingDelta are of different types makes that more challenging. I guess the duplication is probably better than alternatives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I tried to write it with ref locals before I realized the types don't match.

@@ -43,6 +43,9 @@ internal sealed class SymbolicRegexBuilder<TElement> where TElement : notnull
// states that have been created
internal HashSet<DfaMatchingState<TElement>> _stateCache = new();

// capturing states that have been created
internal HashSet<DfaMatchingState<TElement>> _capturingStateCache = new();
Copy link
Member

Choose a reason for hiding this comment

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

Nit: it'd be nice if these were readonly; we can clean that up subsequently, though.

@@ -64,6 +67,7 @@ internal sealed class SymbolicRegexBuilder<TElement> where TElement : notnull
/// </summary>
internal DfaMatchingState<TElement>[]? _statearray;
internal DfaMatchingState<TElement>[]? _delta;
internal DfaMatchingState<TElement>[]? _capturingStatearray;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: it'd be nice if these were _stateArray and _capturingStateArray, but we can clean those up subsequently

@olsaarik
Copy link
Contributor Author

I'm trying to interpret the outerloop failure logs. The possibly relevant failures seem to be linux release and debug, which were killed for OOM, but don't specify during which test. Windows release did fail during Match_VaryingLengthStrings_Huge, but due to a timeout. Also, the log is marking a concurrent test PatternsDataSet_ConstructRegexForAll_SourceGenerated as long running, so I'm not sure what to make of it. Windows debug didn't encounter those timeouts, which is surprising to me.

Osx debug failed on the StressTestDeepNestingOfLoops test, but that's separate from what this PR is addressing. Windows release also had two of those: 1 and 2.

Anyone have any further insight?

@stephentoub
Copy link
Member

stephentoub commented Feb 16, 2022

PatternsDataSet_ConstructRegexForAll_SourceGenerated shouldn't have anything to do with your changes, since that doesn't involve NonBacktracking. That test is quite intensive, though, effectively taking over the machine doing lots and lots of compilation. My guess is that it was running concurrently with StressTestDeepNestingOfLoops, which does use NonBacktracking, and the two of them in combination, with your changes, pushed it over the edge to the point where the whole suite timed out. Same in another run for Match_VaryingLengthStrings_Huge.

@stephentoub
Copy link
Member

These tests were failing before this PR as well, right? If we believe this PR is necessary but not sufficient, we could merge it and then move on to solving the next part of the puzzle.

@olsaarik
Copy link
Contributor Author

Indeed, this is necessary, but perhaps not sufficient (if that OOM was still from NonBacktracking). And the deep nesting of loops tests still seems to require more attention.

I'll merge.

@olsaarik olsaarik merged commit 5dc2868 into dotnet:main Feb 16, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Mar 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants