Use LockFreeReaderHashtable for some of the NodeCaches (#79805)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Mon, 19 Dec 2022 22:33:16 +0000 (07:33 +0900)
committerGitHub <noreply@github.com>
Mon, 19 Dec 2022 22:33:16 +0000 (07:33 +0900)
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.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/VirtualMethodUseNode.cs

index 54db0ac..7d8fb9e 100644 (file)
@@ -164,9 +164,9 @@ namespace ILCompiler.DependencyAnalysis
 
         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) =>
             {
@@ -253,7 +253,7 @@ namespace ILCompiler.DependencyAnalysis
                 return new PInvokeMethodFixupNode(methodData);
             });
 
-            _methodEntrypoints = new NodeCache<MethodDesc, IMethodNode>(CreateMethodEntrypointNode);
+            _methodEntrypoints = new MethodEntrypointHashtable(this);
 
             _unboxingStubs = new NodeCache<MethodDesc, IMethodNode>(CreateUnboxingStubNode);
 
@@ -292,29 +292,16 @@ namespace ILCompiler.DependencyAnalysis
                 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.
@@ -401,13 +388,7 @@ namespace ILCompiler.DependencyAnalysis
                 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 =>
             {
@@ -479,7 +460,7 @@ namespace ILCompiler.DependencyAnalysis
             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 +488,7 @@ namespace ILCompiler.DependencyAnalysis
             }
         }
 
-        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 +522,23 @@ namespace ILCompiler.DependencyAnalysis
             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)
         {
@@ -557,10 +554,16 @@ namespace ILCompiler.DependencyAnalysis
 
             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)
         {
@@ -571,7 +574,7 @@ namespace ILCompiler.DependencyAnalysis
 
             Debug.Assert(!TypeCannotHaveEEType(type));
 
-            return _constructedTypeSymbols.GetOrAdd(type);
+            return _constructedTypeSymbols.GetOrCreateValue(type);
         }
 
         private NodeCache<TypeDesc, IEETypeNode> _clonedTypeSymbols;
@@ -758,11 +761,28 @@ namespace ILCompiler.DependencyAnalysis
             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;
@@ -789,7 +809,18 @@ namespace ILCompiler.DependencyAnalysis
             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;
 
@@ -800,7 +831,7 @@ namespace ILCompiler.DependencyAnalysis
                 return _unboxingStubs.GetOrAdd(method);
             }
 
-            return _methodEntrypoints.GetOrAdd(method);
+            return _methodEntrypoints.GetOrCreateValue(method);
         }
 
         public MethodAssociatedDataNode MethodAssociatedData(IMethodNode methodNode)
@@ -863,10 +894,26 @@ namespace ILCompiler.DependencyAnalysis
             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[][] {
@@ -932,11 +979,28 @@ namespace ILCompiler.DependencyAnalysis
             }
         }
 
-        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;
index efa665a..2a637c4 100644 (file)
@@ -22,6 +22,8 @@ namespace ILCompiler.DependencyAnalysis
     {
         private readonly MethodDesc _decl;
 
+        public MethodDesc Method => _decl;
+
         public VirtualMethodUseNode(MethodDesc decl)
         {
             Debug.Assert(!decl.IsRuntimeDeterminedExactMethod);