From f7f1f37cc7f9022c53d9d664c85a85055f3be949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 19 Dec 2022 14:24:45 +0900 Subject: [PATCH 1/2] Use LockFreeReaderHashtable for some of the NodeCaches 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. --- .../DependencyAnalysis/NodeFactory.cs | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 54db0ac27ca52..392c8ec32e1cd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -164,9 +164,9 @@ public TValue GetOrAdd(TKey key, Func creator) private void CreateNodeCaches() { - _typeSymbols = new NodeCache(CreateNecessaryTypeNode); + _typeSymbols = new NecessaryTypeSymbolHashtable(this); - _constructedTypeSymbols = new NodeCache(CreateConstructedTypeNode); + _constructedTypeSymbols = new ConstructedTypeSymbolHashtable(this); _clonedTypeSymbols = new NodeCache((TypeDesc type) => { @@ -253,7 +253,7 @@ private void CreateNodeCaches() return new PInvokeMethodFixupNode(methodData); }); - _methodEntrypoints = new NodeCache(CreateMethodEntrypointNode); + _methodEntrypoints = new MethodEntrypointHashtable(this); _unboxingStubs = new NodeCache(CreateUnboxingStubNode); @@ -479,7 +479,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)) @@ -507,7 +507,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)); @@ -541,7 +541,23 @@ protected virtual ISymbolDefinitionNode CreateThreadStaticsNode(MetadataType typ return new ThreadStaticsNode(type, this); } - private NodeCache _typeSymbols; + private abstract class TypeSymbolHashtable : LockFreeReaderHashtable + { + 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) { @@ -557,10 +573,16 @@ public IEETypeNode NecessaryTypeSymbol(TypeDesc type) Debug.Assert(!TypeCannotHaveEEType(type)); - return _typeSymbols.GetOrAdd(type); + return _typeSymbols.GetOrCreateValue(type); } - private NodeCache _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) { @@ -571,7 +593,7 @@ public IEETypeNode ConstructedTypeSymbol(TypeDesc type) Debug.Assert(!TypeCannotHaveEEType(type)); - return _constructedTypeSymbols.GetOrAdd(type); + return _constructedTypeSymbols.GetOrCreateValue(type); } private NodeCache _clonedTypeSymbols; @@ -789,7 +811,18 @@ public IMethodNode StringAllocator(MethodDesc stringConstructor) return _stringAllocators.GetOrAdd(stringConstructor); } - protected NodeCache _methodEntrypoints; + private sealed class MethodEntrypointHashtable : LockFreeReaderHashtable + { + 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 _unboxingStubs; private NodeCache _methodAssociatedData; @@ -800,7 +833,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) From db2f7db35bbf90b219f2d8e86410d0906943b9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 19 Dec 2022 15:51:25 +0900 Subject: [PATCH 2/2] Couple more --- .../DependencyAnalysis/NodeFactory.cs | 95 ++++++++++++------- .../VirtualMethodUseNode.cs | 2 + 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 392c8ec32e1cd..7d8fb9eeff544 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -292,29 +292,16 @@ private void CreateNodeCaches() return new ObjectGetTypeFlowDependenciesNode(type); }); - _shadowConcreteMethods = new NodeCache(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 method) => + _shadowConcreteUnboxingMethods = new NodeCache(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 method) => { // We don't need to track virtual method uses for types that have a vtable with a known layout. @@ -401,13 +388,7 @@ private void CreateNodeCaches() return new StructMarshallingDataNode(type); }); - _vTableNodes = new NodeCache((TypeDesc type ) => - { - if (CompilationModuleGroup.ShouldProduceFullVTable(type)) - return new EagerlyBuiltVTableSliceNode(type); - else - return _vtableSliceProvider.GetSlice(type); - }); + _vTableNodes = new VTableSliceHashtable(this); _methodGenericDictionaries = new NodeCache(method => { @@ -780,11 +761,28 @@ public PInvokeMethodFixupNode PInvokeMethodFixup(PInvokeMethodData methodData) return _pInvokeMethodFixups.GetOrAdd(methodData); } - private NodeCache _vTableNodes; + private sealed class VTableSliceHashtable : LockFreeReaderHashtable + { + 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 _methodGenericDictionaries; @@ -896,10 +894,26 @@ internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(Metadat return _objectGetTypeFlowDependencies.GetOrAdd(type); } - private NodeCache _shadowConcreteMethods; + private sealed class ShadowConcreteMethodHashtable : LockFreeReaderHashtable + { + 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 _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[][] { @@ -965,11 +979,28 @@ public MethodDesc InstanceMethodRemovedHelper } } - private NodeCache _virtMethods; + private sealed class VirtualMethodUseHashtable : LockFreeReaderHashtable + { + 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 VirtualMethodUse(MethodDesc decl) { - return _virtMethods.GetOrAdd(decl); + return _virtMethods.GetOrCreateValue(decl); } private NodeCache _variantMethods; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs index efa665ae18c7f..2a637c46a77d6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs @@ -22,6 +22,8 @@ internal sealed class VirtualMethodUseNode : DependencyNodeCore { private readonly MethodDesc _decl; + public MethodDesc Method => _decl; + public VirtualMethodUseNode(MethodDesc decl) { Debug.Assert(!decl.IsRuntimeDeterminedExactMethod);