-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Devirtualize TokenMap #47274
Devirtualize TokenMap #47274
Conversation
Co-authored-by: Youssef Victor <31348972+Youssef1313@users.noreply.github.com>
/// <summary> | ||
/// Used to devirtualize Dictionary/HashSet for EqualityComparer{T}.Default | ||
/// </summary> | ||
internal struct IReferenceOrISignatureEquivalent : IEquatable<IReferenceOrISignatureEquivalent> |
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 not sure I understand why we would need both of these: it seems like just IReferenceOrISignatureEquivalent
should be fine.
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.
IReferenceOrISignature
uses ReferenceEquals
for equals and IReferenceOrISignatureEquivalent
has a more complex equals, previously provided by MetadataEntityReferenceComparer.cs
The dictionary, hashtable and concurrentdictionary will devirtualize (and potentially inline) the equals for a struct when the Comparer is unspecified (if it also implements IEquatable
) in .NET Core; whereas specifying a Comparer will be a slower interface call.
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.
This would be great info to have in a comment in the file itself :)
In reply to: 480276103 [](ancestors = 480276103)
items[(int)token] = item.AsObject(); | ||
|
||
// Update the updated array reference before updating _count | ||
Volatile.Write(ref _items, items); |
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.
Why is this safe? It seems very likely to me that _items could have been changed by this point.
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.
This is the only location _items
is updated and it is done in a lock. The Volatile.Write
is to ensure its not reordered with the write to _count
; so it's safe to Volatile.Read
them in reverse order in GetAllItems()
since _count
only ever increases
object[] items = Volatile.Read(ref _items); | ||
|
||
// Return a right sized copy of the array | ||
return (new ReadOnlySpan<object>(items, 0, count)).ToArray(); |
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.
This doesn't seem particularly safe to me either.
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.
Consider that AddItem
does volatile writes in the following order: _items
then _count
. This method now reads them volatiley in the other order _count
then _items
. That means by the time we read _count
here we know there are at least _count
elements in the _items
collection hence the array returned here will be well formed (no null
values).
There is a subtle invariant change from the previous version of the code:
- Before: If this method was entered when
AddItem
was executing then it would return the effect of completing theAddItem
method - After: If this method was entered when
AddItem
was executing then it may or may not return the effect of completing theAddItem
method.
Don't believe we care about maintaining that invariant.
Done review pass (commit 3). I'm very concerned by the threading in this PR: I think a full writeup of the strategy being taken here and the threading implications is necessary for review. |
object[] items = Volatile.Read(ref _items); | ||
|
||
// Return a right sized copy of the array | ||
return (new ReadOnlySpan<object>(items, 0, count)).ToArray(); |
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.
Consider that AddItem
does volatile writes in the following order: _items
then _count
. This method now reads them volatiley in the other order _count
then _items
. That means by the time we read _count
here we know there are at least _count
elements in the _items
collection hence the array returned here will be well formed (no null
values).
There is a subtle invariant change from the previous version of the code:
- Before: If this method was entered when
AddItem
was executing then it would return the effect of completing theAddItem
method - After: If this method was entered when
AddItem
was executing then it may or may not return the effect of completing theAddItem
method.
Don't believe we care about maintaining that invariant.
Co-authored-by: Jared Parsons <jaredpparsons@gmail.com>
6126910
to
7753168
Compare
@@ -46,7 +47,7 @@ public override void Visit(IEventDefinition eventDefinition) | |||
|
|||
public override void Visit(IFieldReference fieldReference) | |||
{ | |||
if (!_alreadySeen.Add(fieldReference)) | |||
if (!_alreadySeen.Add(new IReferenceOrISignatureEquivalent(fieldReference))) |
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.
Shame can't do implicit conversion from an interface 😉
REstarting CI |
Hello @333fred! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
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.
Auto-approval
Thanks @benaadams! |
Dictionary<T>
(netcoreapp) in TokenMap by using astruct
typestruct
typeAddItem
, so should reduce contention)HastSet<T>
s (net5 Rewrite HashSet<T>'s implementation based on Dictionary<T>'s runtime#37180) in ReferenceIndexerBaseobject[]
back directly rather than passing it as anIEnumerable<object>
and then iterating it to copy it to anobject[]
Was showing up as a hot path in when building Rosyln