-
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
Changes from 3 commits
b3afe69
7a9aee7
86dcb4b
7262313
658fbe3
7753168
0d53ed8
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 |
---|---|---|
|
@@ -7,9 +7,8 @@ | |
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
using Roslyn.Utilities; | ||
using System.Threading; | ||
using Microsoft.Cci; | ||
|
||
namespace Microsoft.CodeAnalysis.CodeGen | ||
{ | ||
|
@@ -24,74 +23,91 @@ namespace Microsoft.CodeAnalysis.CodeGen | |
/// </summary> | ||
internal sealed class TokenMap | ||
{ | ||
private readonly ConcurrentDictionary<object, uint> _itemIdentityToToken = new ConcurrentDictionary<object, uint>(ReferenceEqualityComparer.Instance); | ||
private readonly ConcurrentDictionary<IReferenceOrISignature, uint> _itemIdentityToToken = new(); | ||
private readonly Dictionary<IReferenceOrISignatureEquivalent, uint> _itemEquivalentToToken = new(); | ||
private object[] _items = Array.Empty<object>(); | ||
private int _count = 0; | ||
|
||
private readonly Dictionary<object, uint> _itemToToken; | ||
private readonly ArrayBuilder<object> _items = new ArrayBuilder<object>(); | ||
internal TokenMap() | ||
{ | ||
} | ||
|
||
internal TokenMap(IEqualityComparer<object> comparer) | ||
public uint GetOrAddTokenFor(IReference item, out bool referenceAdded) | ||
{ | ||
_itemToToken = new Dictionary<object, uint>(comparer); | ||
if (_itemIdentityToToken.TryGetValue(new IReferenceOrISignature(item), out uint token)) | ||
{ | ||
referenceAdded = false; | ||
return token; | ||
} | ||
|
||
return AddItem(new IReferenceOrISignatureEquivalent(item), out referenceAdded); | ||
} | ||
|
||
public uint GetOrAddTokenFor(object item, out bool referenceAdded) | ||
public uint GetOrAddTokenFor(ISignature item, out bool referenceAdded) | ||
{ | ||
uint tmp; | ||
if (_itemIdentityToToken.TryGetValue(item, out tmp)) | ||
if (_itemIdentityToToken.TryGetValue(new IReferenceOrISignature(item), out uint token)) | ||
{ | ||
referenceAdded = false; | ||
return (uint)tmp; | ||
return token; | ||
} | ||
|
||
return AddItem(item, out referenceAdded); | ||
return AddItem(new IReferenceOrISignatureEquivalent(item), out referenceAdded); | ||
} | ||
|
||
private uint AddItem(object item, out bool referenceAdded) | ||
private uint AddItem(IReferenceOrISignatureEquivalent item, out bool referenceAdded) | ||
{ | ||
Debug.Assert(item is Cci.ISignature || item is Cci.IReference); | ||
uint token; | ||
|
||
// NOTE: cannot use GetOrAdd here since items and itemToToken must be in sync | ||
// so if we do need to add we have to take a lock and modify both collections. | ||
lock (_items) | ||
lock (_itemEquivalentToToken) | ||
{ | ||
if (!_itemToToken.TryGetValue(item, out token)) | ||
// Check if there is an equivalent type that has a token | ||
if (!_itemEquivalentToToken.TryGetValue(item, out token)) | ||
{ | ||
token = (uint)_items.Count; | ||
_items.Add(item); | ||
_itemToToken.Add(item, token); | ||
token = (uint)_count; | ||
// No equivalent, add the token for this type | ||
_itemEquivalentToToken.Add(item, token); | ||
|
||
var count = (int)token + 1; | ||
var items = _items; | ||
if (items.Length > count) | ||
{ | ||
items[(int)token] = item.AsObject(); | ||
} | ||
else | ||
{ | ||
// Not enough room, we need to resize the array | ||
Array.Resize(ref items, Math.Max(8, count * 2)); | ||
items[(int)token] = item.AsObject(); | ||
|
||
// Update the updated array reference before updating _count | ||
Volatile.Write(ref _items, items); | ||
} | ||
|
||
Volatile.Write(ref _count, count); | ||
} | ||
} | ||
|
||
// Use the provided token to update the reference dictionary | ||
referenceAdded = _itemIdentityToToken.TryAdd(item, token); | ||
return token; | ||
} | ||
|
||
public object GetItem(uint token) | ||
{ | ||
lock (_items) | ||
{ | ||
return _items[(int)token]; | ||
} | ||
} | ||
|
||
public IEnumerable<object> GetAllItems() | ||
{ | ||
lock (_items) | ||
{ | ||
return _items.ToArray(); | ||
} | ||
return _items[(int)token]; | ||
} | ||
|
||
//TODO: why is this is called twice during emit? | ||
// should probably return ROA instead of IE and cache that in Module. (and no need to return count) | ||
public IEnumerable<object> GetAllItemsAndCount(out int count) | ||
public object[] GetAllItems() | ||
{ | ||
lock (_items) | ||
{ | ||
count = _items.Count; | ||
return _items.ToArray(); | ||
} | ||
// Read the count prior to getting the array | ||
int count = Volatile.Read(ref _count); | ||
object[] items = Volatile.Read(ref _items); | ||
|
||
// Return a right sized copy of the array | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return (new ReadOnlySpan<object>(items, 0, count)).ToArray(); | ||
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 doesn't seem particularly safe to me either. 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. Consider that There is a subtle invariant change from the previous version of the code:
Don't believe we care about maintaining that invariant.
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using Microsoft.Cci; | ||
using Microsoft.CodeAnalysis.Symbols; | ||
|
||
namespace Microsoft.CodeAnalysis | ||
{ | ||
/// <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 commentThe 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 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.
The dictionary, hashtable and concurrentdictionary will devirtualize (and potentially inline) the equals for a struct when the Comparer is unspecified (if it also implements 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 would be great info to have in a comment in the file itself :) In reply to: 480276103 [](ancestors = 480276103)
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly object? _item; | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public IReferenceOrISignatureEquivalent(IReference item) | ||
{ | ||
_item = item; | ||
} | ||
|
||
public IReferenceOrISignatureEquivalent(ISignature item) | ||
{ | ||
_item = item; | ||
} | ||
|
||
public IReferenceOrISignatureEquivalent(IMethodReference item) | ||
{ | ||
_item = item; | ||
} | ||
|
||
public bool Equals(IReferenceOrISignatureEquivalent other) | ||
{ | ||
object? x = _item; | ||
object? y = other._item; | ||
if (x is null) | ||
{ | ||
return y is null; | ||
} | ||
else if (ReferenceEquals(x, y)) | ||
{ | ||
return true; | ||
} | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return Equals(x, y); | ||
} | ||
|
||
private new static bool Equals(object? x, object? y) | ||
{ | ||
if (x is null) | ||
{ | ||
return y is null; | ||
} | ||
else if (ReferenceEquals(x, y)) | ||
{ | ||
return true; | ||
} | ||
else if (x is ISymbolInternal sx && y is ISymbolInternal sy) | ||
{ | ||
return sx.Equals(sy, TypeCompareKind.ConsiderEverything); | ||
} | ||
else if (x is ISymbolCompareKindComparableInternal cx && y is ISymbolCompareKindComparableInternal cy) | ||
{ | ||
return cx.Equals(cy, TypeCompareKind.ConsiderEverything); | ||
} | ||
else | ||
{ | ||
return x.Equals(y); | ||
} | ||
} | ||
|
||
public override bool Equals(object? obj) | ||
{ | ||
return obj is IReferenceOrISignatureEquivalent refOrSig ? | ||
Equals(refOrSig) : | ||
Equals(_item, obj); | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public override int GetHashCode() => _item?.GetHashCode() ?? 0; | ||
|
||
public override string ToString() => _item?.ToString() ?? "null"; | ||
|
||
internal object AsObject() => _item!; | ||
} | ||
|
||
/// <summary> | ||
/// Used to devirtualize ConcurrentDictionary for EqualityComparer{T}.Default and ReferenceEquals | ||
/// </summary> | ||
internal struct IReferenceOrISignature : IEquatable<IReferenceOrISignature> | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly object? _item; | ||
|
||
public static implicit operator IReferenceOrISignature(IReferenceOrISignatureEquivalent d) => new IReferenceOrISignature(d.AsObject()); | ||
|
||
private IReferenceOrISignature(object? item) | ||
{ | ||
_item = item; | ||
} | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public IReferenceOrISignature(IReference item) | ||
{ | ||
_item = item; | ||
} | ||
|
||
public IReferenceOrISignature(ISignature item) | ||
{ | ||
_item = item; | ||
} | ||
|
||
public bool Equals(IReferenceOrISignature other) | ||
{ | ||
object? x = _item; | ||
object? y = other._item; | ||
|
||
return ReferenceEquals(x, y); | ||
} | ||
|
||
public override bool Equals(object? obj) | ||
{ | ||
return obj is IReferenceOrISignature refOrSig ? | ||
benaadams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Equals(refOrSig) : | ||
ReferenceEquals(_item, obj); | ||
} | ||
|
||
public override int GetHashCode() => _item?.GetHashCode() ?? 0; | ||
|
||
public override string ToString() => _item?.ToString() ?? "null"; | ||
} | ||
} |
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. TheVolatile.Write
is to ensure its not reordered with the write to_count
; so it's safe toVolatile.Read
them in reverse order inGetAllItems()
since_count
only ever increases