-
Notifications
You must be signed in to change notification settings - Fork 511
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
Changes from 2 commits
16c310e
b5fd4b5
3386603
12b6f93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,15 +65,17 @@ internal static bool CanWrapObject(object obj, Type underlyingType) | |
return false; | ||
} | ||
|
||
ConcurrentDictionary<Type, bool> wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<Type, bool>()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 This line does not need to change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just kept the |
||
|
||
// Avoid creating the delegate if the value already exists | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 This comment is sufficient for the method (it already states the complete intent, and it's just a simple bug that we didn't adhere to it) |
||
bool canCast; | ||
if (!wrappedObject.TryGetValue(obj.GetType(), out canCast)) | ||
if (!SupportedObjectWrappers.TryGetValue(underlyingType, out var wrappedObject)) | ||
{ | ||
wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<Type, bool>()); | ||
} | ||
|
||
// Avoid creating the delegate and capture class | ||
if (!wrappedObject.TryGetValue(obj.GetType(), out var canCast)) | ||
{ | ||
canCast = wrappedObject.GetOrAdd( | ||
obj.GetType(), | ||
kind => underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
@@ -93,15 +95,17 @@ internal static bool CanWrapNode(SyntaxNode node, Type underlyingType) | |
return false; | ||
} | ||
|
||
ConcurrentDictionary<SyntaxKind, bool> wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<SyntaxKind, bool>()); | ||
|
||
// Avoid creating the delegate if the value already exists | ||
bool canCast; | ||
if (!wrappedSyntax.TryGetValue(node.Kind(), out canCast)) | ||
if (!SupportedSyntaxWrappers.TryGetValue(underlyingType, out var wrappedSyntax)) | ||
{ | ||
wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<SyntaxKind, bool>()); | ||
} | ||
|
||
// Avoid creating the 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; | ||
|
@@ -121,15 +125,17 @@ internal static bool CanWrapOperation(IOperation operation, Type underlyingType) | |
return false; | ||
} | ||
|
||
ConcurrentDictionary<OperationKind, bool> wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, _ => new ConcurrentDictionary<OperationKind, bool>()); | ||
|
||
// Avoid creating the delegate if the value already exists | ||
bool canCast; | ||
if (!wrappedSyntax.TryGetValue(operation.Kind, out canCast)) | ||
if (!SupportedOperationWrappers.TryGetValue(underlyingType, out var wrappedSyntax)) | ||
{ | ||
wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary<OperationKind, bool>()); | ||
} | ||
|
||
// Avoid creating the 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; | ||
|
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.
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>());
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.
The IL confirmed that the delegate instance is indeed cached between calls in a static field: