Skip to content

Commit

Permalink
Use LockFreeReaderHashtable for some of the NodeCaches (#79805)
Browse files Browse the repository at this point in the history
Not replacing everything because the only reason this has better perf than `ConcurrentDictionary` is that the API surface is totally unergonomic. These are the heaviest hitters.
  • Loading branch information
MichalStrehovsky authored Dec 19, 2022
1 parent 73b5ac0 commit a23fe3a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ public TValue GetOrAdd(TKey key, Func<TKey, TValue> creator)

private void CreateNodeCaches()
{
_typeSymbols = new NodeCache<TypeDesc, IEETypeNode>(CreateNecessaryTypeNode);
_typeSymbols = new NecessaryTypeSymbolHashtable(this);

_constructedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>(CreateConstructedTypeNode);
_constructedTypeSymbols = new ConstructedTypeSymbolHashtable(this);

_clonedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>((TypeDesc type) =>
{
Expand Down Expand Up @@ -253,7 +253,7 @@ private void CreateNodeCaches()
return new PInvokeMethodFixupNode(methodData);
});

_methodEntrypoints = new NodeCache<MethodDesc, IMethodNode>(CreateMethodEntrypointNode);
_methodEntrypoints = new MethodEntrypointHashtable(this);

_unboxingStubs = new NodeCache<MethodDesc, IMethodNode>(CreateUnboxingStubNode);

Expand Down Expand Up @@ -292,29 +292,16 @@ private void CreateNodeCaches()
return new ObjectGetTypeFlowDependenciesNode(type);
});

_shadowConcreteMethods = new NodeCache<MethodKey, IMethodNode>(methodKey =>
{
MethodDesc canonMethod = methodKey.Method.GetCanonMethodTarget(CanonicalFormKind.Specific);
if (methodKey.IsUnboxingStub)
{
return new ShadowConcreteUnboxingThunkNode(methodKey.Method, MethodEntrypoint(canonMethod, true));
}
else
{
return new ShadowConcreteMethodNode(methodKey.Method, MethodEntrypoint(canonMethod));
}
});
_shadowConcreteMethods = new ShadowConcreteMethodHashtable(this);

_virtMethods = new NodeCache<MethodDesc, VirtualMethodUseNode>((MethodDesc method) =>
_shadowConcreteUnboxingMethods = new NodeCache<MethodDesc, ShadowConcreteUnboxingThunkNode>(method =>
{
// We don't need to track virtual method uses for types that have a vtable with a known layout.
// It's a waste of CPU time and memory.
Debug.Assert(!VTable(method.OwningType).HasFixedSlots);
return new VirtualMethodUseNode(method);
MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
return new ShadowConcreteUnboxingThunkNode(method, MethodEntrypoint(canonMethod, true));
});

_virtMethods = new VirtualMethodUseHashtable(this);

_variantMethods = new NodeCache<MethodDesc, VariantInterfaceMethodUseNode>((MethodDesc method) =>
{
// We don't need to track virtual method uses for types that have a vtable with a known layout.
Expand Down Expand Up @@ -401,13 +388,7 @@ private void CreateNodeCaches()
return new StructMarshallingDataNode(type);
});

_vTableNodes = new NodeCache<TypeDesc, VTableSliceNode>((TypeDesc type ) =>
{
if (CompilationModuleGroup.ShouldProduceFullVTable(type))
return new EagerlyBuiltVTableSliceNode(type);
else
return _vtableSliceProvider.GetSlice(type);
});
_vTableNodes = new VTableSliceHashtable(this);

_methodGenericDictionaries = new NodeCache<MethodDesc, ISortableSymbolNode>(method =>
{
Expand Down Expand Up @@ -479,7 +460,7 @@ protected virtual ISymbolNode CreateGenericLookupFromTypeNode(ReadyToRunGenericH
return new ReadyToRunGenericLookupFromTypeNode(this, helperKey.HelperId, helperKey.Target, helperKey.DictionaryOwner);
}

protected virtual IEETypeNode CreateNecessaryTypeNode(TypeDesc type)
private IEETypeNode CreateNecessaryTypeNode(TypeDesc type)
{
Debug.Assert(!_compilationModuleGroup.ShouldReferenceThroughImportTable(type));
if (_compilationModuleGroup.ContainsType(type))
Expand Down Expand Up @@ -507,7 +488,7 @@ protected virtual IEETypeNode CreateNecessaryTypeNode(TypeDesc type)
}
}

protected virtual IEETypeNode CreateConstructedTypeNode(TypeDesc type)
private IEETypeNode CreateConstructedTypeNode(TypeDesc type)
{
// Canonical definition types are *not* constructed types (call NecessaryTypeSymbol to get them)
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
Expand Down Expand Up @@ -541,7 +522,23 @@ protected virtual ISymbolDefinitionNode CreateThreadStaticsNode(MetadataType typ
return new ThreadStaticsNode(type, this);
}

private NodeCache<TypeDesc, IEETypeNode> _typeSymbols;
private abstract class TypeSymbolHashtable : LockFreeReaderHashtable<TypeDesc, IEETypeNode>
{
protected readonly NodeFactory _factory;
public TypeSymbolHashtable(NodeFactory factory) => _factory = factory;
protected override bool CompareKeyToValue(TypeDesc key, IEETypeNode value) => key == value.Type;
protected override bool CompareValueToValue(IEETypeNode value1, IEETypeNode value2) => value1.Type == value2.Type;
protected override int GetKeyHashCode(TypeDesc key) => key.GetHashCode();
protected override int GetValueHashCode(IEETypeNode value) => value.Type.GetHashCode();
}

private sealed class NecessaryTypeSymbolHashtable : TypeSymbolHashtable
{
public NecessaryTypeSymbolHashtable(NodeFactory factory) : base(factory) { }
protected override IEETypeNode CreateValueFromKey(TypeDesc key) => _factory.CreateNecessaryTypeNode(key);
}

private NecessaryTypeSymbolHashtable _typeSymbols;

public IEETypeNode NecessaryTypeSymbol(TypeDesc type)
{
Expand All @@ -557,10 +554,16 @@ public IEETypeNode NecessaryTypeSymbol(TypeDesc type)

Debug.Assert(!TypeCannotHaveEEType(type));

return _typeSymbols.GetOrAdd(type);
return _typeSymbols.GetOrCreateValue(type);
}

private NodeCache<TypeDesc, IEETypeNode> _constructedTypeSymbols;
private sealed class ConstructedTypeSymbolHashtable : TypeSymbolHashtable
{
public ConstructedTypeSymbolHashtable(NodeFactory factory) : base(factory) { }
protected override IEETypeNode CreateValueFromKey(TypeDesc key) => _factory.CreateConstructedTypeNode(key);
}

private ConstructedTypeSymbolHashtable _constructedTypeSymbols;

public IEETypeNode ConstructedTypeSymbol(TypeDesc type)
{
Expand All @@ -571,7 +574,7 @@ public IEETypeNode ConstructedTypeSymbol(TypeDesc type)

Debug.Assert(!TypeCannotHaveEEType(type));

return _constructedTypeSymbols.GetOrAdd(type);
return _constructedTypeSymbols.GetOrCreateValue(type);
}

private NodeCache<TypeDesc, IEETypeNode> _clonedTypeSymbols;
Expand Down Expand Up @@ -758,11 +761,28 @@ public PInvokeMethodFixupNode PInvokeMethodFixup(PInvokeMethodData methodData)
return _pInvokeMethodFixups.GetOrAdd(methodData);
}

private NodeCache<TypeDesc, VTableSliceNode> _vTableNodes;
private sealed class VTableSliceHashtable : LockFreeReaderHashtable<TypeDesc, VTableSliceNode>
{
private readonly NodeFactory _factory;
public VTableSliceHashtable(NodeFactory factory) => _factory = factory;
protected override bool CompareKeyToValue(TypeDesc key, VTableSliceNode value) => key == value.Type;
protected override bool CompareValueToValue(VTableSliceNode value1, VTableSliceNode value2) => value1.Type == value2.Type;
protected override VTableSliceNode CreateValueFromKey(TypeDesc key)
{
if (_factory.CompilationModuleGroup.ShouldProduceFullVTable(key))
return new EagerlyBuiltVTableSliceNode(key);
else
return _factory._vtableSliceProvider.GetSlice(key);
}
protected override int GetKeyHashCode(TypeDesc key) => key.GetHashCode();
protected override int GetValueHashCode(VTableSliceNode value) => value.Type.GetHashCode();
}

private VTableSliceHashtable _vTableNodes;

public VTableSliceNode VTable(TypeDesc type)
{
return _vTableNodes.GetOrAdd(type);
return _vTableNodes.GetOrCreateValue(type);
}

private NodeCache<MethodDesc, ISortableSymbolNode> _methodGenericDictionaries;
Expand All @@ -789,7 +809,18 @@ public IMethodNode StringAllocator(MethodDesc stringConstructor)
return _stringAllocators.GetOrAdd(stringConstructor);
}

protected NodeCache<MethodDesc, IMethodNode> _methodEntrypoints;
private sealed class MethodEntrypointHashtable : LockFreeReaderHashtable<MethodDesc, IMethodNode>
{
private readonly NodeFactory _factory;
public MethodEntrypointHashtable(NodeFactory factory) => _factory = factory;
protected override bool CompareKeyToValue(MethodDesc key, IMethodNode value) => key == value.Method;
protected override bool CompareValueToValue(IMethodNode value1, IMethodNode value2) => value1.Method == value2.Method;
protected override IMethodNode CreateValueFromKey(MethodDesc key) => _factory.CreateMethodEntrypointNode(key);
protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode();
protected override int GetValueHashCode(IMethodNode value) => value.Method.GetHashCode();
}

private MethodEntrypointHashtable _methodEntrypoints;
private NodeCache<MethodDesc, IMethodNode> _unboxingStubs;
private NodeCache<IMethodNode, MethodAssociatedDataNode> _methodAssociatedData;

Expand All @@ -800,7 +831,7 @@ public IMethodNode MethodEntrypoint(MethodDesc method, bool unboxingStub = false
return _unboxingStubs.GetOrAdd(method);
}

return _methodEntrypoints.GetOrAdd(method);
return _methodEntrypoints.GetOrCreateValue(method);
}

public MethodAssociatedDataNode MethodAssociatedData(IMethodNode methodNode)
Expand Down Expand Up @@ -863,10 +894,26 @@ internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(Metadat
return _objectGetTypeFlowDependencies.GetOrAdd(type);
}

private NodeCache<MethodKey, IMethodNode> _shadowConcreteMethods;
private sealed class ShadowConcreteMethodHashtable : LockFreeReaderHashtable<MethodDesc, ShadowConcreteMethodNode>
{
private readonly NodeFactory _factory;
public ShadowConcreteMethodHashtable(NodeFactory factory) => _factory = factory;
protected override bool CompareKeyToValue(MethodDesc key, ShadowConcreteMethodNode value) => key == value.Method;
protected override bool CompareValueToValue(ShadowConcreteMethodNode value1, ShadowConcreteMethodNode value2) => value1.Method == value2.Method;
protected override ShadowConcreteMethodNode CreateValueFromKey(MethodDesc key) =>
new ShadowConcreteMethodNode(key, _factory.MethodEntrypoint(key.GetCanonMethodTarget(CanonicalFormKind.Specific)));
protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode();
protected override int GetValueHashCode(ShadowConcreteMethodNode value) => value.Method.GetHashCode();
}

private ShadowConcreteMethodHashtable _shadowConcreteMethods;
private NodeCache<MethodDesc, ShadowConcreteUnboxingThunkNode> _shadowConcreteUnboxingMethods;
public IMethodNode ShadowConcreteMethod(MethodDesc method, bool isUnboxingStub = false)
{
return _shadowConcreteMethods.GetOrAdd(new MethodKey(method, isUnboxingStub));
if (isUnboxingStub)
return _shadowConcreteUnboxingMethods.GetOrAdd(method);
else
return _shadowConcreteMethods.GetOrCreateValue(method);
}

private static readonly string[][] s_helperEntrypointNames = new string[][] {
Expand Down Expand Up @@ -932,11 +979,28 @@ public MethodDesc InstanceMethodRemovedHelper
}
}

private NodeCache<MethodDesc, VirtualMethodUseNode> _virtMethods;
private sealed class VirtualMethodUseHashtable : LockFreeReaderHashtable<MethodDesc, VirtualMethodUseNode>
{
private readonly NodeFactory _factory;
public VirtualMethodUseHashtable(NodeFactory factory) => _factory = factory;
protected override bool CompareKeyToValue(MethodDesc key, VirtualMethodUseNode value) => key == value.Method;
protected override bool CompareValueToValue(VirtualMethodUseNode value1, VirtualMethodUseNode value2) => value1.Method == value2.Method;
protected override VirtualMethodUseNode CreateValueFromKey(MethodDesc key)
{
// We don't need to track virtual method uses for types that have a vtable with a known layout.
// It's a waste of CPU time and memory.
Debug.Assert(!_factory.VTable(key.OwningType).HasFixedSlots);
return new VirtualMethodUseNode(key);
}
protected override int GetKeyHashCode(MethodDesc key) => key.GetHashCode();
protected override int GetValueHashCode(VirtualMethodUseNode value) => value.Method.GetHashCode();
}

private VirtualMethodUseHashtable _virtMethods;

public DependencyNodeCore<NodeFactory> VirtualMethodUse(MethodDesc decl)
{
return _virtMethods.GetOrAdd(decl);
return _virtMethods.GetOrCreateValue(decl);
}

private NodeCache<MethodDesc, VariantInterfaceMethodUseNode> _variantMethods;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal sealed class VirtualMethodUseNode : DependencyNodeCore<NodeFactory>
{
private readonly MethodDesc _decl;

public MethodDesc Method => _decl;

public VirtualMethodUseNode(MethodDesc decl)
{
Debug.Assert(!decl.IsRuntimeDeterminedExactMethod);
Expand Down

0 comments on commit a23fe3a

Please sign in to comment.