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
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal abstract class RetargetingParameterSymbol : WrappedParameterSymbol
/// </summary>
private ImmutableArray<CSharpAttributeData> _lazyCustomAttributes;

private TypeWithAnnotations? _lazyTypeWithAnnotations;
sharwell marked this conversation as resolved.
Show resolved Hide resolved

protected RetargetingParameterSymbol(ParameterSymbol underlyingParameter)
: base(underlyingParameter)
{
Expand All @@ -38,7 +40,12 @@ public sealed override TypeWithAnnotations TypeWithAnnotations
{
get
{
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.

}

return _lazyTypeWithAnnotations.Value;
}
}

Expand Down