Conversation
This change defers the acquisition of LookupResult instances from the shared pool to a point where they are known to be needed. In highly-concurrent compilation scenarios (using AnalyzerRunner), this change was observed to reduce overall allocations in the compiler by up to 50% (30GiB) when processing Roslyn.sln.
| End If | ||
|
|
||
| result?.MergeOverloadedOrPrioritized(currentResult, True) | ||
| currentResult?.Free() |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| End If | ||
|
|
||
| result?.MergeOverloadedOrPrioritized(currentResult, checkIfCurrentHasOverloads:=False) | ||
| currentResult?.Free() |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
CC @dotnet/roslyn-compiler |
| Dim currentResult As LookupResult = Nothing | ||
| LookupWithoutInheritance(currentResult, currentType, name, arity, options, accessThroughType, binder, useSiteDiagnostics) | ||
| If result.IsGoodOrAmbiguous AndAlso currentResult.IsGoodOrAmbiguous AndAlso Not LookupResult.CanOverload(result.Symbols(0), currentResult.Symbols(0)) Then | ||
| If result IsNot Nothing AndAlso result.IsGoodOrAmbiguous AndAlso currentResult IsNot Nothing AndAlso currentResult.IsGoodOrAmbiguous AndAlso Not LookupResult.CanOverload(result.Symbols(0), currentResult.Symbols(0)) Then |
There was a problem hiding this comment.
Nit: consider breaking this long line #Closed
There was a problem hiding this comment.
➡️ Resolved by removing the need for the additional null checks that increased the line length #Resolved
| ' Match the native compiler which reports ERR_XmlFeaturesNotAvailable in this case. | ||
| Dim useSiteError = ErrorFactory.ErrorInfo(ERRID.ERR_XmlFeaturesNotAvailable) | ||
| singleResult = New SingleLookupResult(LookupResultKind.NotReferencable, binder.GetErrorSymbol(name, useSiteError), useSiteError) | ||
| singleResult = New SingleLookupResult(LookupResultKind.NotReferencable, Binder.GetErrorSymbol(name, useSiteError), useSiteError) |
There was a problem hiding this comment.
Binder [](start = 92, length = 6)
Looks like this is an incorrect casing change
There was a problem hiding this comment.
The target of the invocation is static
| ExitForFor: | ||
|
|
||
| If ambiguityDiagnostics IsNot Nothing Then | ||
| If lookupResult Is Nothing Then |
There was a problem hiding this comment.
This is nested inside a check for lookupResult IsNot Nothing, should never be true.
| currentResult.Free() | ||
|
|
||
| If methodsOnly AndAlso lookupResult.IsGood Then | ||
| If methodsOnly AndAlso lookupResult IsNot Nothing AndAlso lookupResult.IsGood Then |
There was a problem hiding this comment.
lookupResult IsNot Nothing AndAlso lookupResult.IsGood [](start = 39, length = 54)
Perhaps outside the scope of this PR, but the second half of this if exactly matches the next if. We should just combine them and nest the first part. #Closed
There was a problem hiding this comment.
Actually, wait, nevermind. I missed that this could change the result.
In reply to: 184231979 [](ancestors = 184231979)
| If constraintClass IsNot Nothing Then | ||
| LookupInClass(result, constraintClass, name, arity, options, constraintClass, binder, useSiteDiagnostics) | ||
| If result.StopFurtherLookup Then | ||
| If result IsNot Nothing AndAlso result.StopFurtherLookup Then |
There was a problem hiding this comment.
Nit: feels like all of these similar cases are perfect use cases for result?.Property = True
There was a problem hiding this comment.
Nit: feels like all of these similar cases are perfect use cases for result?.Property = True
The comparison with true is not needed in VB
In reply to: 184232418 [](ancestors = 184232418)
| lookupResult.HasSymbol AndAlso | ||
| lookupResult.Symbols(0).Kind <> SymbolKind.Method Then | ||
| If lookupResult IsNot Nothing AndAlso | ||
| methodsOnly AndAlso |
There was a problem hiding this comment.
Nit: formatting is off by a space on these lines.
There was a problem hiding this comment.
No, I just verified this is the formatting applied by the IDE.
|
A few questions: is this an issue for C#? If so, do we want to fix things there? If it's not an issue, do we know why not? |
|
|
||
| ' Add all viable members from type lookup | ||
| If result.IsGood Then | ||
| If result IsNot Nothing AndAlso result.IsGood Then |
|
|
||
| tmp.Free() | ||
| If result.IsGood Then | ||
| If result IsNot Nothing AndAlso result.IsGood Then |
|
Should this change go to master? |
| If Not result.HasSymbol Then | ||
| result.SetFrom(nonViable) | ||
| If result Is Nothing OrElse Not result.HasSymbol Then | ||
| If result Is Nothing AndAlso nonViable.Kind <> LookupResultKind.Empty Then |
There was a problem hiding this comment.
nonViable.Kind <> LookupResultKind.Empty [](start = 49, length = 40)
It feels like this check might miss something, consider using !nonViable.IsClear instead
There was a problem hiding this comment.
The result Is Nothing isn't needed on the inner if, as it is covered by the outer if.
| End If | ||
|
|
||
| lookupResult.MergePrioritized(singleResult) | ||
| If lookupResult Is Nothing And singleResult.Kind <> LookupResultKind.Empty Then |
There was a problem hiding this comment.
And [](start = 43, length = 3)
AndAlso?
There was a problem hiding this comment.
💭 The branch didn't seem to offer anything here
|
|
||
| ' If no viable or ambiguous results, look in Object. | ||
| If Not lookupResult.IsGoodOrAmbiguous AndAlso (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0 Then | ||
| If lookupResult Is Nothing OrElse (Not lookupResult.IsGoodOrAmbiguous AndAlso (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0) Then |
There was a problem hiding this comment.
lookupResult Is Nothing OrElse (Not lookupResult.IsGoodOrAmbiguous AndAlso (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0) [](start = 19, length = 141)
It feels like grouping is wrong here. Should (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0) be checked even when lookupResult Is Nothing? #Closed
| End If | ||
| lookupResult.MergePrioritized(currentResult) | ||
|
|
||
| If lookupResult Is Nothing AndAlso currentResult.Kind <> LookupResultKind.Empty Then |
There was a problem hiding this comment.
currentResult.Kind <> LookupResultKind.Empty [](start = 59, length = 45)
!IsClear?
There was a problem hiding this comment.
An empty RHS is a NOP in MergePrioritized. I can make the change if you wish for future-proofing or some other reason, but it would not change the current behavior.
| If Not result.HasSymbol Then | ||
| result.SetFrom(nonViable) | ||
| If result Is Nothing OrElse Not result.HasSymbol Then | ||
| If result Is Nothing AndAlso nonViable.Kind <> LookupResultKind.Empty Then |
There was a problem hiding this comment.
If result Is Nothing AndAlso nonViable.Kind <> LookupResultKind.Empty Then [](start = 20, length = 74)
It feels fragile that we are basing the logic here on intimate knowledge of what SetFrom is going to do. One of the reasons that logic was extracted is to avoid duplicating it in multiple places and here (and in other similar places) we are reintroducing the duplication, making it unsafe to modify SetFrom's implementation. The same issue probably exists around other "merge" helpers that are surrounded with custom logic to pre-allocate/avoid-invocation logic.
It feels like the code could be somewhat simpler and more allocations could probably be avoided if we convert all the affected SetFrom/"merge" helpers into extension methods that take "this" parameter ByRef and allocate on as needed bases inside them. Ideally, in this file we should only be left with null checks before dereferencing and no code that tries to predict what helpers are going to do.
|
Done with review pass (iteration 2). |
This problem was not observed in C# during profiling.
The change is based on dev15.7.x and can merge into anything forward of that. It's a very high impact change for machines with many processors. In the least noticable case, allocations processing Roslyn.sln with both C# and VB analyzers enabled were reduced by 15.3% (20GB). |
* Use VB handling of nullable conditions * Remove unnecessary null check * Fix logic error in condition
1e3b904 to
3b7ad0c
Compare
| ' it may look like a Good result, but it may have ambiguities inside | ||
| ' so we need to check that to be sure. | ||
| If lookupResult.IsGood Then | ||
| If lookupResult IsNot Nothing AndAlso lookupResult.IsGood Then |
There was a problem hiding this comment.
Still more instances of lookupResult?.IsGood substitution.
That leads to hte latter part of my question: If it's not an issue, do we know why not? C# also uses a pooled LookupResult object. So something about C# is making it so that the same issue is not observed (even though they're used extensively in c# binding as well). To me, it woudl be worthwhile to understand what compilation pattern C# is employing that makes this a non-issue. It might then be better to adopt that pattern in the VB compiler versus trying to address things from this direction. |
|
|
||
| ' If no viable or ambiguous results, look in Object. | ||
| If Not lookupResult.IsGoodOrAmbiguous AndAlso (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0 Then | ||
| If lookupResult?.IsGoodOrAmbiguous <> True AndAlso (options And LookupOptions.NoSystemObjectLookupForInterfaces) = 0 Then |
There was a problem hiding this comment.
lookupResult?.IsGoodOrAmbiguous <> True [](start = 19, length = 39)
This still looks suspicious. In VB lookupResult?.IsGoodOrAmbiguous <> True will be Boolean? and null value is not true, but I think we would want to continue when lookupResult is Nothing. #Closed
There was a problem hiding this comment.
The original code is Not lookupResult.IsGoodOrAmbiguous. An empty LookupResult would have produced True for this expression. The new expression is intended to treat Nothing the same as empty, and also produce True for the expression.
There was a problem hiding this comment.
The new expression is intended to treat Nothing the same as empty, and also produce True for the expression.
Why do you think so? Comparing nullable null to anything yields null in VB, not true, not false
In reply to: 184547523 [](ancestors = 184547523)
There was a problem hiding this comment.
Is there any short form here? The alternative I know of is this expression:
lookupResult Is Nothing OrElse Not lookupResult.IsGoodOrAmbiguous
There was a problem hiding this comment.
Is there any short form here
I think this is fine. There is an alternative Not If(lookupResult?.IsGoodOrAmbiguous, False), but I think explicit null check is easier to follow.
In reply to: 184820315 [](ancestors = 184820315)
|
Done with review pass (iteration 3). I am still not comfortable with the issue raised in #26398 (review) |
I didn't look into it. This is a targeted fix for a localized/acute problem revealed by profiling. I couldn't find an explanation for why the object pool for VB was missing at the observed rate, but the results were consistent across more than a dozen runs.
I don't think I'm going to be able to address this item. I plan to address the remaining items aside from this one and then leave the final direction open:
|
The <> operator in VB behaves differently from the != operator and C# when comparing nullable values. The semantics of the latter were intended in this code, so the expression is expanded to correct the logic.
I also think we should try to understand what is going on here. Pools are supposed to be fast and they were introduced exactly to make it relatively cheap to "allocate" objects.
|
I could not find any evidence of this.
AnalyzerRunner is intended to represent real-world scenarios as closely as possible. The inner loop can be reviewed here for accuracy:
It's possible this could help, but it's not clear what size would help. It's also possible that the frequency of dropped objects in Another alternative solution is making |
|
It looks like this change delayed LookupResult.GetInstance() calls only in 3 places( out of total 12) where it was called in Binder_Lookup.vb. Two of the delayed calls are inside loops, where LookupResult is allocated and freed on each iteration. |
|
@sharwell I'm marking the PR as private for the moment. Please ping when the PR is ready to review again. |
|
Closing this issue and filed #26779 instead |
This change defers the acquisition of LookupResult instances from the shared pool
to a point where they are known to be needed. In highly-concurrent compilation
scenarios (using AnalyzerRunner), this change was observed to reduce overall
allocations in the compiler by up to 50% (30GiB) when processing Roslyn.sln.
Ask Mode template not completed
Customer scenario
What does the customer do to get into this situation, and why do we think this
is common enough to address for this release. (Granted, sometimes this will be
obvious "Open project, VS crashes" but in general, I need to understand how
common a scenario is)
Bugs this fixes
(either VSO or GitHub links)
Workarounds, if any
Also, why we think they are insufficient for RC vs. RC2, RC3, or RTW
Risk
This is generally a measure our how central the affected code is to adjacent
scenarios and thus how likely your fix is to destabilize a broader area of code
Performance impact
(with a brief justification for that assessment (e.g. "Low perf impact because no extra allocations/no complexity changes" vs. "Low")
Is this a regression from a previous update?
Root cause analysis
How did we miss it? What tests are we adding to guard against it in the future?
How was the bug found?
(E.g. customer reported it vs. ad hoc testing)
Test documentation updated?
If this is a new non-compiler feature or a significant improvement to an existing feature, update https://github.com/dotnet/roslyn/wiki/Manual-Testing once you know which release it is targeting.