Skip to content

lock protect nullability cache of symbolic regex node - #60942

Merged
stephentoub merged 5 commits into
dotnet:mainfrom
veanes:fixThreadSafetyInSymbolicRegex
Nov 3, 2021
Merged

lock protect nullability cache of symbolic regex node#60942
stephentoub merged 5 commits into
dotnet:mainfrom
veanes:fixThreadSafetyInSymbolicRegex

Conversation

@veanes

Copy link
Copy Markdown
Contributor

Added lock to protect SymbolicRegexNode._nullabilityCache that stores conditional nullability of a node for a given context, for thread-safety. This computation is not the common case as it only applies when the node (regex) starts with an anchor and can potentially be nullable (accept the empty string). Initial thought was to special case nullability for context 0 using a field but this is already covered in most common cases when the regex is neither nullable nor can be nullable that is checked before. The cache could potentially be moved to the builder as a shared cache to avoid the caches in the nodes but would then create bigger probability of thread contention at the builder level.

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Text.RegularExpressions and removed community-contribution Indicates that the PR has been added by a community member labels Oct 27, 2021
@ghost

Copy link
Copy Markdown

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

Issue Details

Added lock to protect SymbolicRegexNode._nullabilityCache that stores conditional nullability of a node for a given context, for thread-safety. This computation is not the common case as it only applies when the node (regex) starts with an anchor and can potentially be nullable (accept the empty string). Initial thought was to special case nullability for context 0 using a field but this is already covered in most common cases when the regex is neither nullable nor can be nullable that is checked before. The cache could potentially be moved to the builder as a shared cache to avoid the caches in the nodes but would then create bigger probability of thread contention at the builder level.

Author:veanes
Assignees:-
Labels:

area-System.Text.RegularExpressions

Milestone:-

@veanes
veanes requested a review from eerhardtOctober 27, 2021 23:23
@veanes
veanesforce-pushed the fixThreadSafetyInSymbolicRegex branch from d681d61 to 6e4e4d8CompareOctober 27, 2021 23:39
@stephentoub

Copy link
Copy Markdown
Member

@veanes, I tried running this with our perf tests in dotnet/performance, just with NonBacktracking subbed in for the options. There are a few notable regressions. Is that expected?

MethodToolchainOptionsMeanErrorStdDevMedianMinMaxRatio
Email_IsMatchD:\coreclrtest\pr\corerun.exe1024277.66 ns1.974 ns1.750 ns277.93 ns274.85 ns280.76 ns1.16
Email_IsMatchd:\coreclrtest\main\corerun.exe1024239.41 ns2.505 ns2.092 ns239.24 ns235.91 ns243.58 ns1.00
Email_IsNotMatchD:\coreclrtest\pr\corerun.exe1024282.41 ns2.858 ns2.533 ns282.48 ns277.18 ns285.97 ns1.16
Email_IsNotMatchd:\coreclrtest\main\corerun.exe1024243.20 ns2.135 ns1.783 ns242.66 ns240.78 ns247.10 ns1.00
Date_IsMatchD:\coreclrtest\pr\corerun.exe1024213.16 ns1.070 ns0.949 ns213.15 ns211.64 ns215.33 ns1.22
Date_IsMatchd:\coreclrtest\main\corerun.exe1024175.06 ns1.932 ns1.713 ns174.52 ns172.81 ns178.86 ns1.00
Date_IsNotMatchD:\coreclrtest\pr\corerun.exe1024191.14 ns3.625 ns3.560 ns189.36 ns185.88 ns198.14 ns1.20
Date_IsNotMatchd:\coreclrtest\main\corerun.exe1024159.08 ns0.988 ns0.924 ns159.22 ns157.20 ns160.42 ns1.00
MatchesBoundaryD:\coreclrtest\pr\corerun.exe102445,933.54 ns449.868 ns375.660 ns46,031.74 ns45,154.15 ns46,395.06 ns1.10
MatchesBoundaryd:\coreclrtest\main\corerun.exe102441,639.27 ns398.737 ns372.979 ns41,735.20 ns40,851.51 ns42,095.63 ns1.00

@veanes

Copy link
Copy Markdown
ContributorAuthor

I would not expect any noticeable regressions, because I was expecting the change (locking) not to really affect the hot-path. If I understand correctly, 22% slower for example for Date_IsMatch? What does the regex look like in this case -- I would expect it to make heavy use anchors (\b ?) that then show up in checking nullability that causes locking. If so, we could still create fields for the most common context dependent nullability checks before using the cache and thus avoid locks.

@veanes

Copy link
Copy Markdown
ContributorAuthor

OK, indeed they do indeed use \b a lot at least in two cases, such as @"\b\w{10,}\b", so locking will introduce overhead there and is apparently expensive enough. I'll add specialized fields to cover those cases to avoid locking, I'll push a commit into this PR. Then you could rerun the perf evaluation. I think will be 4 special cases, roughly corresponding to contexts (\w,\w), (\w,\W), (\W,\w) , (\W,\W). Perhaps also for other cases (email example uses ^ and $, as \A and \Z -- I think -- not as line anchors that would require multiline option too)

@veanes

veanes commented Oct 28, 2021

Copy link
Copy Markdown
ContributorAuthor

Alternative would be to use a nested array nullability[][] of size 5x5 in each node.
nullability[prev][next] is 0 initially meaning unknown.
value 1 means true and value 2 means false. I'm assuming the operation

if (nullability[prev][next] == 0) nullability[prev][next] = ComputeNullability();
return nullability[prev][next] == 1;

is thread-safe without any locks.

I believe that would be the more efficient solution that is also uniform for all cases and avoids locking if I'm right about thread safety above.

@veanes

Copy link
Copy Markdown
ContributorAuthor

Also, I forgot to add above, the nullability array would be null in those cases where it is irrelevant and never used, namely, when the node can never be nullable or is always nullable, these are by far the most common cases.

@veanes

Copy link
Copy Markdown
ContributorAuthor

nullability array could also be flat, then it would need size 64 (3 bits per kind) then the lookup would directly use context (that is exactly (next << 3 | prev)).

@veanes
veanesforce-pushed the fixThreadSafetyInSymbolicRegex branch from 6e4e4d8 to 4564b04CompareOctober 28, 2021 22:26
@veanes

Copy link
Copy Markdown
ContributorAuthor

@stephentoub, I updated the fix according to my last comment. I'm therefore asking for a re-review. It would be interesting to know the performance comparison after this change, assuming it is still thread-safe -- which it should be, as it replaces the earlier nullability dictionary with an array and gets rid of locking. I'm using byte[64] to represent the values (to avoid bool?[64]) where I use 0 as UndefByte because it is the default value.

@veanes
veanesforce-pushed the fixThreadSafetyInSymbolicRegex branch from 4564b04 to 3052ed0CompareOctober 29, 2021 06:30
@veanes

Copy link
Copy Markdown
ContributorAuthor

@stephentoub : I took care of the comments and also simplified the initial test in IsNullableFor in the last commit. Once the checks pass the code should be merged into main. Just wanted to note that I don't have merge permission here yet, which I don't really mind (but I also cannot choose reviewers).

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@veanes
veanesforce-pushed the fixThreadSafetyInSymbolicRegex branch 2 times, most recently from 94e6f8d to 0c1aa20CompareNovember 2, 2021 22:15
Sign up for freeto 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

@veanes@stephentoub@eerhardt