未验证 提交 d84e0a04 编写于 作者: A Andy Gocke 提交者: GitHub

Add cache for GetTypeByMetadataName (#28778)

This seems to be a popular call among analyzers, especially XUnit. It's a
reasonable way to acquire a well-known analyzer type, but analyzers often
don't save the resulting symbol. This causes significant CPU work to be done
repeatedly as XUnit looks for the same symbol over and over, passing in the
same type name each time.

A very small, simple cache seems to solve the problem without requiring any
changes on the XUnit side.
上级 a72e08f8
......@@ -13,9 +13,10 @@ namespace Microsoft.CodeAnalysis
{
// very simple cache with a specified size.
// expiration policy is "new entry wins over old entry if hashed into the same bucket"
internal class ConcurrentCache<TKey, TValue> :
CachingBase<ConcurrentCache<TKey, TValue>.Entry> where TKey : IEquatable<TKey>
internal class ConcurrentCache<TKey, TValue> : CachingBase<ConcurrentCache<TKey, TValue>.Entry>
{
private readonly IEqualityComparer<TKey> _keyComparer;
// class, to ensure atomic updates.
internal class Entry
{
......@@ -31,18 +32,22 @@ internal Entry(int hash, TKey key, TValue value)
}
}
public ConcurrentCache(int size)
public ConcurrentCache(int size, IEqualityComparer<TKey> keyComparer)
: base(size)
{
_keyComparer = keyComparer;
}
public ConcurrentCache(int size)
: this(size, EqualityComparer<TKey>.Default) { }
public bool TryAdd(TKey key, TValue value)
{
var hash = key.GetHashCode();
var hash = _keyComparer.GetHashCode(key);
var idx = hash & mask;
var entry = this.entries[idx];
if (entry != null && entry.hash == hash && entry.key.Equals(key))
if (entry != null && entry.hash == hash && _keyComparer.Equals(entry.key, key))
{
return false;
}
......@@ -53,11 +58,11 @@ public bool TryAdd(TKey key, TValue value)
public bool TryGetValue(TKey key, out TValue value)
{
int hash = key.GetHashCode();
int hash = _keyComparer.GetHashCode(key);
int idx = hash & mask;
var entry = this.entries[idx];
if (entry != null && entry.hash == hash && entry.key.Equals(key))
if (entry != null && entry.hash == hash && _keyComparer.Equals(entry.key, key))
{
value = entry.value;
return true;
......
......@@ -902,6 +902,15 @@ public IPointerTypeSymbol CreatePointerTypeSymbol(ITypeSymbol pointedAtType)
protected abstract IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol elementType);
// PERF: ETW Traces show that analyzers may use this method frequently, often requesting
// the same symbol over and over again. XUnit analyzers, in particular, were consuming almost
// 1% of CPU time when building Roslyn itself. This is an extremely simple cache that evicts on
// hash code conflicts, but seems to do the trick. The size is mostly arbitrary. My guess
// is that there are maybe a couple dozen analyzers in the solution and each one has
// ~0-2 unique well-known types, and the chance of hash collision is very low.
private ConcurrentCache<string, INamedTypeSymbol> _getTypeCache =
new ConcurrentCache<string, INamedTypeSymbol>(50, ReferenceEqualityComparer.Instance);
/// <summary>
/// Gets the type within the compilation's assembly and all referenced assemblies (other than
/// those that can only be referenced via an extern alias) using its canonical CLR metadata name.
......@@ -912,7 +921,13 @@ public IPointerTypeSymbol CreatePointerTypeSymbol(ITypeSymbol pointedAtType)
/// </remarks>
public INamedTypeSymbol GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
return CommonGetTypeByMetadataName(fullyQualifiedMetadataName);
if (!_getTypeCache.TryGetValue(fullyQualifiedMetadataName, out var val))
{
val = CommonGetTypeByMetadataName(fullyQualifiedMetadataName);
// Ignore if someone added the same value before us
_ = _getTypeCache.TryAdd(fullyQualifiedMetadataName, val);
}
return val;
}
protected abstract INamedTypeSymbol CommonGetTypeByMetadataName(string metadataName);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册