From ae6a8fd1cec590547729997236dba04b7ef7e755 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 3 Nov 2025 10:04:15 -0800 Subject: [PATCH 1/4] PERF-392: Avoid closure allocation in `.GetCompiler()` --- .../Linq/MemberCompilerProviderTest.cs | 28 +++++++++---------- .../Interfaces/IMemberCompilerProvider{T}.cs | 21 ++++++++++++-- .../MemberCompilerProvider.cs | 23 ++++++++------- .../Orm/Linq/Translator.Expressions.cs | 12 ++++---- .../ExpressionProcessor.Helpers.cs | 6 ++-- 5 files changed, 53 insertions(+), 37 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs b/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs index e9f7efafd5..d4adc8bb76 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs @@ -20,7 +20,7 @@ public partial class MemberCompilerProviderTest { private readonly string[] dummy = new string[10]; - private static Func GetCompilerForMethod( + private static IMemberCompilerProvider.BoundCompiler GetCompilerForMethod( IMemberCompilerProvider provider, Type source, string methodName) { if (source.IsGenericTypeDefinition) @@ -31,11 +31,11 @@ private static Func GetCompilerForMethod( if (mi.IsGenericMethodDefinition) mi = mi.MakeGenericMethod(typeof(object)); var result = provider.GetCompiler(mi); - Assert.IsNotNull(result); + Assert.IsFalse(result.IsNull); return result; } - private static Func GetCompilerForCtor( + private static IMemberCompilerProvider.BoundCompiler GetCompilerForCtor( IMemberCompilerProvider provider, Type source) { if (source.IsGenericTypeDefinition) @@ -43,11 +43,11 @@ private static Func GetCompilerForCtor( var ci = source.GetConstructors().First(); var result = provider.GetCompiler(ci); - Assert.IsNotNull(result); + Assert.IsFalse(result.IsNull); return result; } - private static Func GetCompilerForField( + private static IMemberCompilerProvider.BoundCompiler GetCompilerForField( IMemberCompilerProvider provider, Type source, string fieldName) { if (source.IsGenericTypeDefinition) @@ -56,7 +56,7 @@ private static Func GetCompilerForField( var fi = source.GetField(fieldName); Assert.IsNotNull(fi); var result = provider.GetCompiler(fi); - Assert.IsNotNull(result); + Assert.IsFalse(result.IsNull); return result; } @@ -71,7 +71,7 @@ public void MethodsTest() foreach (string s2 in new[]{"Generic", "NonGeneric"}) { string method = s1 + s2 + "Method"; var d = GetCompilerForMethod(provider, t, method); - Assert.AreEqual(t.Name + "." + method, d(null, dummy)); + Assert.AreEqual(t.Name + "." + method, d.Invoke(null, dummy)); } } @@ -86,7 +86,7 @@ public void PropertiesTest() foreach (string s2 in new[] { "InstanceProperty", "StaticProperty", "Item" }) { string method = s1 + s2; var d = GetCompilerForMethod(provider, t, method); - Assert.AreEqual(t.Name + "." + method, d(null, dummy)); + Assert.AreEqual(t.Name + "." + method, d.Invoke(null, dummy)); } } @@ -99,7 +99,7 @@ public void FieldsTest() foreach (var t in new[]{typeof(NonGenericTarget), typeof(GenericTarget<>)}) foreach (string s in new[] {"InstanceField", "StaticField"}) { var d = GetCompilerForField(provider, t, s); - Assert.AreEqual(t.Name + "." + s, d(null, dummy)); + Assert.AreEqual(t.Name + "." + s, d.Invoke(null, dummy)); } } @@ -110,7 +110,7 @@ public void CtorsTest() provider.RegisterCompilers(typeof(CtorCompiler)); foreach (var t in new[]{typeof(NonGenericTarget), typeof(GenericTarget<>)}) { var d = GetCompilerForCtor(provider, t); - Assert.AreEqual(t.Name + Reflection.WellKnown.CtorName, d(null, dummy)); + Assert.AreEqual(t.Name + Reflection.WellKnown.CtorName, d.Invoke(null, dummy)); } } @@ -128,8 +128,8 @@ public void GenericFindTest() .MakeGenericMethod(typeof(string)); var d = provider.GetCompiler(mi); - Assert.IsNotNull(d); - Assert.AreEqual("OK", d(null, dummy)); + Assert.IsFalse(d.IsNull); + Assert.AreEqual("OK", d.Invoke(null, dummy)); } [Test] @@ -160,7 +160,7 @@ public void ConflictKeepOldTest() provider.RegisterCompilers(typeof(ConflictCompiler1)); provider.RegisterCompilers(typeof(ConflictCompiler2), ConflictHandlingMethod.KeepOld); var d = GetCompilerForMethod(provider, typeof(ConflictTarget), "ConflictMethod"); - Assert.AreEqual("Compiler1", d(null, dummy)); + Assert.AreEqual("Compiler1", d.Invoke(null, dummy)); } [Test] @@ -170,7 +170,7 @@ public void ConflictOverwriteTest() provider.RegisterCompilers(typeof(ConflictCompiler1)); provider.RegisterCompilers(typeof(ConflictCompiler2), ConflictHandlingMethod.Overwrite); var d = GetCompilerForMethod(provider, typeof(ConflictTarget), "ConflictMethod"); - Assert.AreEqual("Compiler2", d(null, dummy)); + Assert.AreEqual("Compiler2", d.Invoke(null, dummy)); } [Test] diff --git a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs index fe4976d1f0..d2e58f9e75 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs @@ -4,8 +4,6 @@ // Created by: Denis Krjuchkov // Created: 2009.02.09 -using System; -using System.Collections.Generic; using System.Reflection; namespace Xtensive.Orm.Linq.MemberCompilation @@ -36,12 +34,29 @@ public interface IMemberCompilerProvider : IMemberCompilerProvider /// Conflict handling method. void RegisterCompilers(IEnumerable>> compilerDefinitions, ConflictHandlingMethod conflictHandlingMethod); + public readonly struct BoundCompiler + { + private readonly Func compiler; + private readonly MemberInfo memberInfo; + + public bool IsNull => compiler == null; + + public T Invoke(T arg2, T[] arg3) => compiler(memberInfo, arg2, arg3); + + public BoundCompiler(Func compiler, MemberInfo memberInfo) + { + ArgumentNullException.ThrowIfNull(compiler); + this.compiler = compiler; + this.memberInfo = memberInfo; + } + } + /// /// Finds compiler for specified . /// /// to search compiler for. /// compiler associated with /// or if compiler is not found. - Func GetCompiler(MemberInfo target); + BoundCompiler GetCompiler(MemberInfo target); } } diff --git a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs index abe1d7d6a5..62f04db980 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/MemberCompilerProvider.cs @@ -4,9 +4,7 @@ // Created by: Denis Krjuchkov // Created: 2009.02.09 -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Frozen; using System.Reflection; using Xtensive.Core; using Xtensive.Reflection; @@ -36,24 +34,25 @@ public CompilerKey(MemberInfo memberInfo) } } - private readonly Dictionary compilers - = new Dictionary(); + private IDictionary compilers = new Dictionary(); public Type ExpressionType => typeof(T); - public Delegate GetUntypedCompiler(MemberInfo target) + public override void Lock(bool recursive) { - ArgumentNullException.ThrowIfNull(target); - - return compilers.GetValueOrDefault(GetCompilerKey(target)); + base.Lock(recursive); + compilers = compilers.ToFrozenDictionary(); } - public Func GetCompiler(MemberInfo target) + public Delegate GetUntypedCompiler(MemberInfo target) { - var compiler = (Func) GetUntypedCompiler(target); - return compiler.Bind(target); + ArgumentNullException.ThrowIfNull(target); + return compilers.TryGetValue(GetCompilerKey(target), out var v) ? v : null; } + public IMemberCompilerProvider.BoundCompiler GetCompiler(MemberInfo target) => + new((Func) GetUntypedCompiler(target), target); + public void RegisterCompilers(Type compilerContainer) { RegisterCompilers(compilerContainer, ConflictHandlingMethod.Default); diff --git a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs index 57a70b1f1c..6e032b21a1 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs @@ -361,18 +361,18 @@ protected override Expression VisitMember(MemberExpression ma) // Reflected type doesn't have custom compiler defined, so falling back to base class compiler var declaringType = memberInfo.DeclaringType; var reflectedType = memberInfo.ReflectedType; - if (customCompiler == null && declaringType != reflectedType && declaringType.IsAssignableFrom(reflectedType)) { + if (customCompiler.IsNull && declaringType != reflectedType && declaringType.IsAssignableFrom(reflectedType)) { var root = declaringType; var current = reflectedType; - while (current != root && customCompiler == null) { + while (current != root && customCompiler.IsNull) { current = current.BaseType; var member = current.GetProperty(memberInfo.Name, BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic); customCompiler = context.CustomCompilerProvider.GetCompiler(member); } } - if (customCompiler != null) { - var expression = customCompiler.Invoke(sourceExpression, Array.Empty()); + if (!customCompiler.IsNull) { + var expression = customCompiler.Invoke(sourceExpression, []); if (expression == null) { if (reflectedType.IsInterface) return Visit(BuildInterfaceExpression(ma)); @@ -435,7 +435,7 @@ protected override Expression VisitMethodCall(MethodCallExpression mc) using (CreateScope(new TranslatorState(State) { IsTailMethod = mc == context.Query && mc.IsQuery() })) { var method = mc.Method; var customCompiler = context.CustomCompilerProvider.GetCompiler(method); - if (customCompiler != null) { + if (!customCompiler.IsNull) { return Visit(customCompiler.Invoke(mc.Object, mc.Arguments.ToArray())); } @@ -1841,7 +1841,7 @@ private Expression BuildExpression(MemberExpression ma, IEnumerable Date: Mon, 3 Nov 2025 10:59:46 -0800 Subject: [PATCH 2/4] allow null compiler --- .../Interfaces/IMemberCompilerProvider{T}.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs index d2e58f9e75..ab28d0ef53 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs @@ -34,21 +34,11 @@ public interface IMemberCompilerProvider : IMemberCompilerProvider /// Conflict handling method. void RegisterCompilers(IEnumerable>> compilerDefinitions, ConflictHandlingMethod conflictHandlingMethod); - public readonly struct BoundCompiler + public readonly struct BoundCompiler(Func compiler, MemberInfo memberInfo) { - private readonly Func compiler; - private readonly MemberInfo memberInfo; - public bool IsNull => compiler == null; public T Invoke(T arg2, T[] arg3) => compiler(memberInfo, arg2, arg3); - - public BoundCompiler(Func compiler, MemberInfo memberInfo) - { - ArgumentNullException.ThrowIfNull(compiler); - this.compiler = compiler; - this.memberInfo = memberInfo; - } } /// From a4d93c494899276d1aac3cfc018e781dda425fbd Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 3 Nov 2025 12:28:33 -0800 Subject: [PATCH 3/4] Fix NonPublicGetterTest --- Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs b/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs index d4adc8bb76..8e3582c136 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/MemberCompilerProviderTest.cs @@ -189,7 +189,7 @@ public void NonPublicGetterTest() .GetProperty("InternalProperty", BindingFlags.Instance | BindingFlags.NonPublic); Assert.IsNotNull(property); var result = provider.GetCompiler(property); - Assert.IsNull(result); + Assert.IsTrue(result.IsNull); } [Test] From df9416668de321a0f55696dad8534f7035b65709 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Mon, 3 Nov 2025 12:30:01 -0800 Subject: [PATCH 4/4] Remove public --- .../MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs index ab28d0ef53..7c16da50ca 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/MemberCompilation/Interfaces/IMemberCompilerProvider{T}.cs @@ -34,7 +34,7 @@ public interface IMemberCompilerProvider : IMemberCompilerProvider /// Conflict handling method. void RegisterCompilers(IEnumerable>> compilerDefinitions, ConflictHandlingMethod conflictHandlingMethod); - public readonly struct BoundCompiler(Func compiler, MemberInfo memberInfo) + readonly struct BoundCompiler(Func compiler, MemberInfo memberInfo) { public bool IsNull => compiler == null;