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

Avoid allocations in CanWrap... methods #3711

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 15 additions & 21 deletions StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ internal static bool CanWrapObject(object obj, Type underlyingType)
return false;
}

ConcurrentDictionary<Type, bool> wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<Type, bool>());
Copy link
Contributor

Choose a reason for hiding this comment

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

Was there an actual problem with this first lookup (in each method)? I haven't dug into your IL comparison, but since no capture is needed, it seems the only possible overhead would be creating the func instance. I think I recall that lambdas are automatically detected to be static if possible, but in case that is not correct, wouldn't simply marking the lambda as static be the best change?

ConcurrentDictionary<Type, bool> wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<Type, bool>());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The IL confirmed that the delegate instance is indeed cached between calls in a static field:

// Load the delegate instance from a static field  
    IL_0033: ldsfld       class System.Func`2<System.Type, ConcurrentDictionary`2<SyntaxKind, bool>> StyleCop.Analyzers.Lightup.LightupHelpers/'<>c'::'<>9__30_0'
    IL_0038: dup
// Branch if not null?
    IL_0039: brtrue.s     IL_0052
    IL_003b: pop
// ..
// Create the delegate
    IL_0047: newobj       instance void class System.Func`2<System.Type, ConcurrentDictionary`2<SyntaxKind, bool>>::.ctor(object, native int)
    IL_004c: dup
// Store it
    IL_004d: stsfld       class System.Func`2<System.Type, ConcurrentDictionary`2<SyntaxKind, bool>> StyleCop.Analyzers.Lightup.LightupHelpers/'<>c'::'<>9__30_0'
// Invoke the delegate
    IL_0052: callvirt     instance !1/*class [System.Collections.Concurrent]System.Collections.Concurrent.ConcurrentDictionary`2<valuetype [Microsoft.CodeAnalysis.CSharp]Microsoft.CodeAnalysis.CSharp.SyntaxKind, bool>*/ class [System.Collections.Concurrent]System.Collections.Concurrent.ConcurrentDictionary`2<class [System.Runtime]System.Type, class [System.Collections.Concurrent]System.Collections.Concurrent.ConcurrentDictionary`2<valuetype [Microsoft.CodeAnalysis.CSharp]Microsoft.CodeAnalysis.CSharp.SyntaxKind, bool>>::GetOrAdd(!0/*class [System.Runtime]System.Type*/, class [System.Runtime]System.Func`2<!0/*class [System.Runtime]System.Type*/, !1/*class [System.Collections.Concurrent]System.Collections.Concurrent.ConcurrentDictionary`2<valuetype [Microsoft.CodeAnalysis.CSharp]Microsoft.CodeAnalysis.CSharp.SyntaxKind, bool>*/>)

Copy link
Member

Choose a reason for hiding this comment

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

📝 This line does not need to change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just kept the static to emphasize the non-capturing semantic of the lambda.

ConcurrentDictionary<Type, bool> wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<Type, bool>());

// Avoid creating the delegate if the value already exists
bool canCast;
if (!wrappedObject.TryGetValue(obj.GetType(), out canCast))
// Avoid creating a delegate and capture class
if (!wrappedObject.TryGetValue(obj.GetType(), out var canCast))
{
canCast = wrappedObject.GetOrAdd(
obj.GetType(),
kind => underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo()));
Copy link
Member

Choose a reason for hiding this comment

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

📝 This line is the source of the capturing allocation (specifically, capturing a method parameter in a lambda forces the the capture to be allocated on all paths and not just the one that creates this delegate).

canCast = underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo());
wrappedObject.TryAdd(obj.GetType(), canCast);
Comment on lines +73 to +74
Copy link
Member

Choose a reason for hiding this comment

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

❗ Requesting that this pull request be reduced to just this two line change in each case.

}

return canCast;
Expand All @@ -93,15 +91,13 @@ internal static bool CanWrapNode(SyntaxNode node, Type underlyingType)
return false;
}

ConcurrentDictionary<SyntaxKind, bool> wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<SyntaxKind, bool>());
ConcurrentDictionary<SyntaxKind, bool> wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<SyntaxKind, bool>());

// Avoid creating the delegate if the value already exists
bool canCast;
if (!wrappedSyntax.TryGetValue(node.Kind(), out canCast))
// Avoid creating a delegate and capture class
if (!wrappedSyntax.TryGetValue(node.Kind(), out var canCast))
{
canCast = wrappedSyntax.GetOrAdd(
node.Kind(),
kind => underlyingType.GetTypeInfo().IsAssignableFrom(node.GetType().GetTypeInfo()));
canCast = underlyingType.GetTypeInfo().IsAssignableFrom(node.GetType().GetTypeInfo());
wrappedSyntax.TryAdd(node.Kind(), canCast);
}

return canCast;
Expand All @@ -121,15 +117,13 @@ internal static bool CanWrapOperation(IOperation operation, Type underlyingType)
return false;
}

ConcurrentDictionary<OperationKind, bool> wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<OperationKind, bool>());
ConcurrentDictionary<OperationKind, bool> wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<OperationKind, bool>());

// Avoid creating the delegate if the value already exists
bool canCast;
if (!wrappedSyntax.TryGetValue(operation.Kind, out canCast))
// Avoid creating a delegate and capture class
if (!wrappedSyntax.TryGetValue(operation.Kind, out var canCast))
{
canCast = wrappedSyntax.GetOrAdd(
operation.Kind,
kind => underlyingType.GetTypeInfo().IsAssignableFrom(operation.GetType().GetTypeInfo()));
canCast = underlyingType.GetTypeInfo().IsAssignableFrom(operation.GetType().GetTypeInfo());
wrappedSyntax.TryAdd(operation.Kind, canCast);
}

return canCast;
Expand Down