-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Cache RetargetingParameterSymbol.TypeWithAnnotations to avoid ~8.3% allocations in a trace #69011
Conversation
…llocations in a trace
07cf776
to
90ec1a6
Compare
return this.RetargetingModule.RetargetingTranslator.Retarget(_underlyingParameter.TypeWithAnnotations, RetargetOptions.RetargetPrimitiveTypesByTypeCode); | ||
if (!_lazyTypeWithAnnotations.HasValue) | ||
{ | ||
_lazyTypeWithAnnotations = this.RetargetingModule.RetargetingTranslator.Retarget(_underlyingParameter.TypeWithAnnotations, RetargetOptions.RetargetPrimitiveTypesByTypeCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nullable<TypeWithAnnotations>
is not thread-safe. Is this field accessed from multiple threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cston Yeah I was chatting with Sam about this. I didn't find an appropriate existing helper, so Sam is working on merging helpers that are not available in the compilers layer with those available in the compilers layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No additional helpers are necessary, that's what https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Symbols/TypeWithAnnotations.cs,e5078e3d5fd03b05,references is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the goal is to avoid recalculating the type, it may be sufficient to just cache the TypeSymbol
.
private TypeSymbol? _lazyType;
public sealed override TypeWithAnnotations TypeWithAnnotations
{
get
{
var underlyingType = _underlyingParameter.TypeWithAnnotations;
var translator = RetargetingModule.RetargetingTranslator;
if (_lazyType is null)
{
Interlocked.CompareExchange(ref _lazyType,
translator.Retarget(underlyingType.Type,
RetargetOptions.RetargetPrimitiveTypesByTypeCode),
null);
}
var newModifiers = translator.RetargetModifiers(underlyingType.CustomModifiers, out _);
return underlyingType.WithTypeAndModifiers(_lazyType, newModifiers);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cston In the trace I provided, this property is accessed 12,833,732 times during 3 minutes. So I think it's worth to just cache the whole thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No additional helpers are necessary, that's what https://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Symbols/TypeWithAnnotations.cs,e5078e3d5fd03b05,references is for.
Does this suggest it like that?
if (_lazyTypeWithAnnotations is null)
{
Interlocked.CompareExchange(ref _lazyTypeWithAnnotations, new TypeWithAnnotations.Boxed(......), null);
}
I think that would still risk calculating it twice if two threads concurrently accessed the property.
What Sam suggested was something to be like this:
return LazyInitialization.EnsureInitialized(
ref _lazyTypeWithAnnotations,
static self => self.RetargetingModule.RetargetingTranslator.Retarget(self._underlyingParameter.TypeWithAnnotations, RetargetOptions.RetargetPrimitiveTypesByTypeCode),
this);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should switch to TypeWithAnnotations.Boxed
, which we use to cache this information in symbols. There should be many examples. No special helpers are necessary and the change shouldn't be blocked on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used TypeWithAnnotations.Boxed
for now. After #69017 is merged, I can switch to another helper if that would be better.
I would like to better understand the scenario causing an issue here. Can you share the code being compiled? |
@333fred The code I'm currently testing with is a private repo. I don't think I'll be able to share it. But I think the repro from #67926 will do the job. It's almost the same idea. The PR #68316 did have a good impact on this scenario, but it doesn't resolve it completely. It's essentially a codebase full of generic extension methods. In a 3 minute trace, |
@333fred In case you wanted me to log more events into the trace, just let me know. |
src/Compilers/CSharp/Portable/Symbols/Retargeting/RetargetingParameterSymbol.cs
Outdated
Show resolved
Hide resolved
Done with review pass (commit 1) |
In addition to the number of times |
Blocked on #69017 |
I want to understand the codepath that caused this. This seems extreme and like some component is misbehaving. I'm sympathetic to the issue, but I don't want to patch over the problem, I want to understand why it is a problem. |
@333fred Does the stacks in PerfView not give enough info about the code path causing this? Note that the scenario is really full of generic extension methods, even ones with multiple overloads. The IDE lookup requests goes through this loop, which keeps creating reduced extension method symbols In I'd like to add this: Apparently, this property is taking 8.2% of CPU time. |
Why is that? Regular |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 2)
@dotnet/roslyn-compiler For the second review |
Do you have a trace showing the 'after' results? Thanks! |
@CyrusNajmabadi Sure. I uploaded it to the same feedback ticket on DC. |
@333fred Given the before/after traces, can this move forward and get merged? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm satisfied by scenario here.
In the trace from https://developercommunity.visualstudio.com/t/Performance-trace-for-Roslyn-team/10413054, there are too many
TypeWithAnnotations[]
allocations happening in RetargetingParameterSymbol (thanks @sharwell for the investigation).I re-profiled the scenario with a locally built version of the compiler that adds some logging to CodeAnalysisEventSource, and @sharwell figured out that on average, there are 76 calls to the exact same
RetargetingParameterSymbol
instance. So we think caching might be beneficial here.I don't know if that will solve the problem completely, we might need to get another trace with the change from this PR and see if there are more bottlenecks, but at least, this seems like a good first step.
Edit:
The previous screenshot from trace shows 3.5% allocations for TypewithAnnotions arrays, however, there is also the following in the same trace:
which is 4.8% of allocations. So total is 8.3%, and there are probably more allocations happening during the evaluation of this property.