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

Cache RetargetingParameterSymbol.TypeWithAnnotations to avoid ~8.3% allocations in a trace #69011

Merged
merged 2 commits into from
Jul 15, 2023

Conversation

Youssef1313
Copy link
Member

@Youssef1313 Youssef1313 commented Jul 12, 2023

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).

image

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:

image

which is 4.8% of allocations. So total is 8.3%, and there are probably more allocations happening during the evaluation of this property.

@Youssef1313 Youssef1313 force-pushed the typewithannotations-cache branch from 07cf776 to 90ec1a6 Compare July 12, 2023 22:04
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Jul 12, 2023
@ghost ghost added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Jul 12, 2023
@Youssef1313 Youssef1313 marked this pull request as ready for review July 13, 2023 04:20
@Youssef1313 Youssef1313 requested a review from a team as a code owner July 13, 2023 04:20
return this.RetargetingModule.RetargetingTranslator.Retarget(_underlyingParameter.TypeWithAnnotations, RetargetOptions.RetargetPrimitiveTypesByTypeCode);
if (!_lazyTypeWithAnnotations.HasValue)
{
_lazyTypeWithAnnotations = this.RetargetingModule.RetargetingTranslator.Retarget(_underlyingParameter.TypeWithAnnotations, RetargetOptions.RetargetPrimitiveTypesByTypeCode);
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

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);
    }
}

Copy link
Member Author

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.

Copy link
Member Author

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);

Copy link
Member

@sharwell sharwell Jul 13, 2023

Choose a reason for hiding this comment

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

@333fred The helpers are lazy initialization helpers for reference types, and are unrelated to the fact that this is a value type. Even after switching to TypeWithAnnotations.Boxed, we'd want to use the helper method for clarity. See #69017

Copy link
Contributor

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.

Copy link
Member Author

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.

@333fred
Copy link
Member

333fred commented Jul 13, 2023

I would like to better understand the scenario causing an issue here. Can you share the code being compiled?

@Youssef1313
Copy link
Member Author

Youssef1313 commented Jul 13, 2023

@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, RetargetingParameterSymbol.TypeWithAnnotations was accessed over 10 million times, to be accurate, 12,833,732.

@Youssef1313
Copy link
Member Author

@333fred In case you wanted me to log more events into the trace, just let me know.

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 1)

@sharwell
Copy link
Member

In addition to the number of times TypeWithAnnotations is accessed, we showed that on average the TypeWithAnnotations property is accessed 76 times for each RetargetingParameterSymbol instance.

@sharwell
Copy link
Member

Blocked on #69017

@sharwell sharwell marked this pull request as draft July 13, 2023 13:42
@sharwell sharwell added Blocked and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Jul 13, 2023
@333fred
Copy link
Member

333fred commented Jul 13, 2023

In a 3 minute trace, RetargetingParameterSymbol.TypeWithAnnotations was accessed over 10 million times, to be accurate, 12,833,732.

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.

@Youssef1313
Copy link
Member Author

Youssef1313 commented Jul 13, 2023

@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

https://github.com/dotnet/roslyn/blob/7c6d5b92a584f0c512b51a0c44a9f441a9443f6f/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs#L1670C39-L1670C106

In ReducedExtensionMethodSymbol.Create, the type of the first parameter is accessed.

I'd like to add this:

image

Apparently, this property is taking 8.2% of CPU time.

@AlekseyTs
Copy link
Contributor

Blocked on #69017

Why is that? Regular Interlocked.CompareExchange just works with TypeWithAnnotations.Boxed.

@AlekseyTs AlekseyTs removed the Blocked label Jul 13, 2023
@AlekseyTs AlekseyTs marked this pull request as ready for review July 13, 2023 15:38
Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (commit 2)

@AlekseyTs
Copy link
Contributor

@dotnet/roslyn-compiler For the second review

@Youssef1313 Youssef1313 changed the title Cache RetargetingParameterSymbol.TypeWithAnnotations to avoid ~3.5% allocations in a trace Cache RetargetingParameterSymbol.TypeWithAnnotations to avoid ~8.3% allocations in a trace Jul 13, 2023
@CyrusNajmabadi
Copy link
Member

Do you have a trace showing the 'after' results? Thanks!

@Youssef1313
Copy link
Member Author

@CyrusNajmabadi Sure. I uploaded it to the same feedback ticket on DC.

@Youssef1313
Copy link
Member Author

@333fred Given the before/after traces, can this move forward and get merged?

Copy link
Member

@333fred 333fred left a 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.

@333fred 333fred merged commit 2fb8ad0 into dotnet:main Jul 15, 2023
@ghost ghost added this to the Next milestone Jul 15, 2023
@Youssef1313 Youssef1313 deleted the typewithannotations-cache branch July 15, 2023 03:17
@allisonchou allisonchou modified the milestones: Next, 17.8 P1 Jul 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants