You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Perf] Linux/x64: 2 Regressions in Regex on 7/11/2024 6:16:28 PM #104975
changed the title [-][Perf] Linux/x64: 2 Regressions on 7/11/2024 6:16:28 PM[/-][+][Perf] Linux/x64: 2 Regressions in Regex on 7/11/2024 6:16:28 PM[/+]on Jul 16, 2024
My guess is that perf regression by 7%-8% on this (Twain) benchmark is related to a tradeoff in search heuristics
that on the other hand boosted other benchmarks, but I asked @ieviev to take a closer look into this too.
Some trade-offs were definitely expected, I will have a closer look at the benchmarks soon.
There's one substantial regression in dotnet/perf-autofiling-issues#38275, going from 55.81 μs to 76.42 μs with (?m)^Sherlock Holmes|Sherlock Holmes$.
I remember the patterns .{0,2}(Tom|Sawyer|Huckleberry|Finn) and .{2,4}(Tom|Sawyer|Huckleberry|Finn) were faster than before, perhaps it was from some changes along the way.
The problem here is that we're doing more initial checks per match to speed up the inner loop which causes the 5% slowdown here, but we never really use the inner loop to its full extent either. These two benchmarks could be ~25% faster with findOptimizations disabled.
if(findOptimizations.FindModeisFindNextStartingPositionMode.LeadingSet_LeftToRight or FindNextStartingPositionMode.FixedDistanceSets_LeftToRight){if(findOptimizations.FixedDistanceSets![0].Negated){_findOpts=null;}}else{_findOpts=findOptimizations;}
This would disable avx for searching negated sets like [^\n]. In the end of the PR i decided to avoid special casing since it's complex and hard to know when it's slower/faster but perhaps turning it off for large negated sets is reliable enough and speeds things up in most cases. Generally i think the speedup in other cases is big enough to justify some losses around 5% here and there, but there will be input-dependent tradeoffs either way.
I'd be ok special-casing negated sets for non-backtracking, if we measure on our known set of benchmarks and demonstrate that it does more good than harm. I'd prefer, however, we do it as part of actually picking the optimizations, e.g. at
That said, I'm still nervous about possible ramifications of turning off the find optimizations. It's quite possible it's the right choice, but doing it in response to this feels incongruent. We were using these find optimizations with these patterns both before and after the recent round of non-backtracking optimizations, right? So the problem is actually what's cited as "The problem here is that we're doing more initial checks per match to speed up the inner loop which causes the 5% slowdown here"... there's no way to re-reduce that overhead? Which are the initial checks you're referring to?
That said, I'm still nervous about possible ramifications of turning off the find optimizations. It's quite possible it's the right choice, but doing it in response to this feels incongruent. We were using these find optimizations with these patterns both before and after the recent round of non-backtracking optimizations, right? So the problem is actually what's cited as "The problem here is that we're doing more initial checks per match to speed up the inner loop which causes the 5% slowdown here"... there's no way to re-reduce that overhead? Which are the initial checks you're referring to?
Yes, this is the same as before. My initial thoughts were that the overhead could be from the extra work done entering/leaving this function per-match but i'm uncertain about this.
// We successfully transitioned, so update our current input index to match.
pos++;
}
}
finally
{
// Write back the local copies of the ref values.
posRef=pos;
endPosRef=endPos;
currentStateIdRef=currStateId;
}
}
What i wanted to point out that if the performance of this individual benchmark is important then a lot more could be gained elsewhere. I understand it's better for maintainability overall to not make the engine too different from the rest, so it's definitely OK leaving it on as well.
Profiling said that 99% of the time is spent in FindEndPositionDeltasDFAOptimized, so the only sources of extra loop overhead compared to the pre-PR state i can think of could be this if case after prefix search here:
Another source of overhead could be memory: the minterm lookup array used to be strictly 128 elements for ascii, and the benchmark input only contains ascii, i think . here allocates more memory and perhaps uses a different level of cache.
With that said, the performance loss itself the way i see it is marginal. If there's no large performance losses and in exchange we increased performance somewhere else then it's fine leaving things as is. If ASCII performance is vital then making the engine work on bytes directly would benefit a lot more. if Regex.Count performance is vital then it could be a separate loop that doesn't do extra work per-match. If consistency is vital then findOptimizations could always be off to work like re2. It's not possible to win in all cases without making sacrifices somewhere.
Let me also add here that I agree with Ian here. I don't see it worthwhile to special case it and think we could leave it as is. I am going to meet with Ian in a couple of weeks in Estonia where one thing we will discuss is further steps for other optimizations.
Activity
[-][Perf] Linux/x64: 2 Regressions on 7/11/2024 6:16:28 PM[/-][+][Perf] Linux/x64: 2 Regressions in Regex on 7/11/2024 6:16:28 PM[/+]dotnet-policy-service commentedon Jul 16, 2024
Tagging subscribers to this area: @dotnet/area-system-text-regularexpressions
See info in area-owners.md if you want to be subscribed.
DrewScoggins commentedon Jul 16, 2024
Seems related to this pr, #102655.
veanes commentedon Jul 16, 2024
My guess is that perf regression by 7%-8% on this (Twain) benchmark is related to a tradeoff in search heuristics
that on the other hand boosted other benchmarks, but I asked @ieviev to take a closer look into this too.
LoopedBard3 commentedon Jul 16, 2024
Related Regressions:
ericstj commentedon Jul 17, 2024
cc @stephentoub @veanes @ieviev
ieviev commentedon Jul 17, 2024
Some trade-offs were definitely expected, I will have a closer look at the benchmarks soon.
There's one substantial regression in dotnet/perf-autofiling-issues#38275, going from 55.81 μs to 76.42 μs with
(?m)^Sherlock Holmes|Sherlock Holmes$.I remember the patterns
.{0,2}(Tom|Sawyer|Huckleberry|Finn)and.{2,4}(Tom|Sawyer|Huckleberry|Finn)were faster than before, perhaps it was from some changes along the way.ieviev commentedon Jul 17, 2024
This reproduces on my machine as well:

The problem here is that we're doing more initial checks per match to speed up the inner loop which causes the 5% slowdown here, but we never really use the inner loop to its full extent either. These two benchmarks could be ~25% faster with findOptimizations disabled.
The change would be to turn this line here:
runtime/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs
Line 213 in 27776e2
This would disable avx for searching negated sets like
[^\n]. In the end of the PR i decided to avoid special casing since it's complex and hard to know when it's slower/faster but perhaps turning it off for large negated sets is reliable enough and speeds things up in most cases. Generally i think the speedup in other cases is big enough to justify some losses around 5% here and there, but there will be input-dependent tradeoffs either way.@stephentoub thoughts on special casing this?
ieviev commentedon Jul 17, 2024
This does not reproduce on my machine at all, can someone else confirm this?
stephentoub commentedon Jul 22, 2024
I'd be ok special-casing negated sets for non-backtracking, if we measure on our known set of benchmarks and demonstrate that it does more good than harm. I'd prefer, however, we do it as part of actually picking the optimizations, e.g. at
runtime/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexFindOptimizations.cs
Lines 223 to 239 in fcb9b18
rather than subsequently determining in the non-backtracking engine whether to store the ones already picked.
stephentoub commentedon Jul 22, 2024
That said, I'm still nervous about possible ramifications of turning off the find optimizations. It's quite possible it's the right choice, but doing it in response to this feels incongruent. We were using these find optimizations with these patterns both before and after the recent round of non-backtracking optimizations, right? So the problem is actually what's cited as "The problem here is that we're doing more initial checks per match to speed up the inner loop which causes the 5% slowdown here"... there's no way to re-reduce that overhead? Which are the initial checks you're referring to?
ieviev commentedon Jul 22, 2024
@stephentoub
Yes, this is the same as before. My initial thoughts were that the overhead could be from the extra work done entering/leaving this function per-match but i'm uncertain about this.
runtime/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs
Lines 625 to 729 in 2746c03
What i wanted to point out that if the performance of this individual benchmark is important then a lot more could be gained elsewhere. I understand it's better for maintainability overall to not make the engine too different from the rest, so it's definitely OK leaving it on as well.
Profiling said that 99% of the time is spent in
FindEndPositionDeltasDFAOptimized, so the only sources of extra loop overhead compared to the pre-PR state i can think of could be this if case after prefix search here:runtime/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexMatcher.cs
Lines 663 to 675 in 2746c03
Another source of overhead could be memory: the minterm lookup array used to be strictly 128 elements for ascii, and the benchmark input only contains ascii, i think
.here allocates more memory and perhaps uses a different level of cache.With that said, the performance loss itself the way i see it is marginal. If there's no large performance losses and in exchange we increased performance somewhere else then it's fine leaving things as is. If ASCII performance is vital then making the engine work on bytes directly would benefit a lot more. if Regex.Count performance is vital then it could be a separate loop that doesn't do extra work per-match. If consistency is vital then findOptimizations could always be off to work like re2. It's not possible to win in all cases without making sacrifices somewhere.
veanes commentedon Jul 26, 2024
Let me also add here that I agree with Ian here. I don't see it worthwhile to special case it and think we could leave it as is. I am going to meet with Ian in a couple of weeks in Estonia where one thing we will discuss is further steps for other optimizations.
stephentoub commentedon Aug 3, 2024
The regressions were indeed fixed (more than fixed) by #105668:
veanes commentedon Aug 5, 2024
Awesome that you figured out the fix for these regressions Stephen. Thank you!