Skip to content

Commit 8968d24

Browse files
authored
Merge pull request #23 from servicetitan/expression-lambda
Workaround of Expression.Lambda performance degradation
2 parents a07ff48 + 4c663a3 commit 8968d24

21 files changed

Lines changed: 171 additions & 46 deletions

Extensions/Xtensive.Orm.BulkOperations/Internals/ExpressionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Linq.Expressions;
4+
using Xtensive.Linq;
45
using Xtensive.Reflection;
56

67
namespace Xtensive.Orm.BulkOperations
@@ -23,7 +24,7 @@ public static bool IsContainsQuery(this Expression expression)
2324

2425
internal static object Invoke(this Expression expression)
2526
{
26-
return Expression.Lambda(typeof (Func<>).MakeGenericType(expression.Type), expression).Compile().DynamicInvoke();
27+
return FastExpression.Lambda(typeof (Func<>).MakeGenericType(expression.Type), expression).Compile().DynamicInvoke();
2728
}
2829

2930
internal static Expression Visit<T>(this Expression exp, Func<T, Expression> visitor) where T : Expression

Extensions/Xtensive.Orm.BulkOperations/Internals/SetOperation.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Xtensive.Sql;
88
using Xtensive.Sql.Dml;
99
using Xtensive.Core;
10+
using Xtensive.Linq;
1011

1112
namespace Xtensive.Orm.BulkOperations
1213
{
@@ -139,7 +140,7 @@ private void AddConstantValue(AddValueContext addContext)
139140
{
140141
SqlTableColumn column = SqlDml.TableColumn(addContext.Statement.Table, addContext.Field.Column.Name);
141142
SqlExpression value;
142-
object constant = Expression.Lambda(addContext.Lambda.Body, null).Compile().DynamicInvoke();
143+
object constant = FastExpression.Lambda(addContext.Lambda.Body, null).Compile().DynamicInvoke();
143144
if (constant==null)
144145
value = SqlDml.Null;
145146
else {
@@ -199,7 +200,7 @@ private void AddEntityValue(AddValueContext addContext)
199200
i++;
200201
ParameterExpression p = Expression.Parameter(info.UnderlyingType);
201202
LambdaExpression lambda =
202-
Expression.Lambda(
203+
FastExpression.Lambda(
203204
typeof (Func<,>).MakeGenericType(info.UnderlyingType, field.ValueType),
204205
Expression.MakeMemberAccess(p, field.UnderlyingProperty),
205206
p);
@@ -215,7 +216,7 @@ private void AddEntityValue(AddValueContext addContext)
215216
}
216217
}
217218
i = -1;
218-
var entity = (IEntity) Expression.Lambda(addContext.Lambda.Body, null).Compile().DynamicInvoke();
219+
var entity = (IEntity) FastExpression.Lambda(addContext.Lambda.Body, null).Compile().DynamicInvoke();
219220
foreach (ColumnInfo column in addContext.Field.Columns) {
220221
i++;
221222
SqlExpression value;
@@ -238,7 +239,7 @@ public void AddValues()
238239
var addContext = new AddValueContext {
239240
Descriptor = descriptor,
240241
Lambda =
241-
Expression.Lambda(
242+
FastExpression.Lambda(
242243
typeof (Func<,>).MakeGenericType(typeof (T), descriptor.Expression.Type),
243244
descriptor.Expression,
244245
descriptor.Parameter),

Extensions/Xtensive.Orm.Security/SecureQueryRootBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Linq;
1010
using System.Linq.Expressions;
11+
using Xtensive.Linq;
1112
using Xtensive.Orm;
1213

1314
namespace Xtensive.Orm.Security
@@ -54,7 +55,7 @@ private IQueryable<T> GetSecureQuery<T>(ImpersonationContext context) where T: c
5455
continue;
5556
}
5657
var p = Expression.Parameter(queryType, "p");
57-
var where = (Expression<Func<T, bool>>) Expression.Lambda(Expression.Not(Expression.TypeIs(p, permissionType)), p);
58+
var where = (Expression<Func<T, bool>>) FastExpression.Lambda(Expression.Not(Expression.TypeIs(p, permissionType)), p);
5859
candidates.Add(InsecureQuery.All<T>().Where(where).Concat(permission.Query(context, InsecureQuery).OfType<T>()));
5960
}
6061
// Query<Dog> && Permission<Animal>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (C) 2020 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Linq.Expressions;
7+
using NUnit.Framework;
8+
using Xtensive.Core;
9+
using Xtensive.Linq;
10+
11+
namespace Xtensive.Orm.Tests.Core.Linq
12+
{
13+
[TestFixture]
14+
public class LambdaExpressionFactoryTests
15+
{
16+
private readonly Type delegateType = typeof(Func<int, int>);
17+
private readonly Expression<Func<int, int>> plusOne = i => i + 1;
18+
19+
[Test]
20+
public void ShouldUseFastFactory() => Assert.That(LambdaExpressionFactory.CanUseFastFactory());
21+
22+
[Test]
23+
public void ShouldCreateFactoryFast()
24+
{
25+
var lambda = (Func<int, int>) LambdaExpressionFactory
26+
.CreateFactoryFast(delegateType)
27+
.Invoke(plusOne.Body, plusOne.Parameters.ToArray())
28+
.Compile();
29+
30+
Assert.That(lambda.Invoke(1), Is.EqualTo(2));
31+
}
32+
33+
[Test]
34+
public void ShouldCreateFactorySlow()
35+
{
36+
var lambda = (Func<int, int>) LambdaExpressionFactory.Instance
37+
.CreateFactorySlow(delegateType)
38+
.Invoke(plusOne.Body, plusOne.Parameters.ToArray())
39+
.Compile();
40+
41+
Assert.That(lambda.Invoke(1), Is.EqualTo(2));
42+
}
43+
44+
[Test]
45+
public void ShouldCreateLambda()
46+
{
47+
var lambda = (Func<int, int>) LambdaExpressionFactory.Instance
48+
.CreateLambda(delegateType, plusOne.Body, plusOne.Parameters.ToArray())
49+
.Compile();
50+
51+
Assert.That(lambda.Invoke(1), Is.EqualTo(2));
52+
}
53+
}
54+
}

Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ public static LambdaExpression StripQuotes(this Expression expression)
242242
if (!typeof (LambdaExpression).IsAssignableFrom(expression.Type))
243243
throw new InvalidOperationException(string.Format("Unable to process expression '{0}'", expression));
244244
var typeAs = Expression.TypeAs(expression, typeof (LambdaExpression));
245-
return Expression.Lambda<Func<LambdaExpression>>(typeAs).CachingCompile()();
245+
return FastExpression.Lambda<Func<LambdaExpression>>(typeAs).CachingCompile()();
246246
}
247247
return lambda;
248248
}

Orm/Xtensive.Orm/Linq/ExpressionVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ protected override Expression VisitLambda(LambdaExpression l)
150150
{
151151
Expression body = Visit(l.Body);
152152
if (body!=l.Body)
153-
return Expression.Lambda(l.Type, body, l.Parameters);
153+
return FastExpression.Lambda(l.Type, body, l.Parameters);
154154
return l;
155155
}
156156

Orm/Xtensive.Orm/Linq/FastExpression.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ public static LambdaExpression Lambda(Type delegateType, Expression body, params
2929
return LambdaExpressionFactory.Instance.CreateLambda(delegateType, body, parameters);
3030
}
3131

32+
/// <summary>
33+
/// Generates <see cref="LambdaExpression"/> faster than <see cref="Expression.Lambda(Type,Expression,ParameterExpression[])"/>.
34+
/// </summary>
35+
/// <param name="delegateType">A type that represents a delegate type.</param>
36+
/// <param name="body">The body of lambda expression.</param>
37+
/// <param name="parameters">The parameters of lambda expression.</param>
38+
/// <returns>Constructed lambda expression.</returns>
39+
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters)
40+
{
41+
return (Expression<TDelegate>) LambdaExpressionFactory.Instance.CreateLambda(typeof(TDelegate), body, parameters);
42+
}
43+
3244
/// <summary>
3345
/// Generates <see cref="LambdaExpression"/> faster than <see cref="Expression.Lambda(Type,Expression,IEnumerable{ParameterExpression})"/>.
3446
/// </summary>

Orm/Xtensive.Orm/Linq/Internals/LambdaExpressionFactory.cs

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,33 @@
1313
using Xtensive.Reflection;
1414

1515
using Factory = System.Func<
16-
System.Linq.Expressions.Expression,
17-
System.Collections.Generic.IEnumerable<System.Linq.Expressions.ParameterExpression>,
18-
System.Linq.Expressions.LambdaExpression
16+
System.Linq.Expressions.Expression,
17+
System.Linq.Expressions.ParameterExpression[],
18+
System.Linq.Expressions.LambdaExpression
19+
>;
20+
21+
using SlowFactory = System.Func<
22+
System.Linq.Expressions.Expression,
23+
System.Collections.Generic.IEnumerable<System.Linq.Expressions.ParameterExpression>,
24+
System.Linq.Expressions.LambdaExpression
25+
>;
26+
27+
using FastFactory = System.Func<
28+
System.Linq.Expressions.Expression,
29+
string,
30+
bool,
31+
System.Collections.Generic.IReadOnlyList<System.Linq.Expressions.ParameterExpression>,
32+
System.Linq.Expressions.LambdaExpression
1933
>;
2034

2135
namespace Xtensive.Linq
2236
{
2337
internal sealed class LambdaExpressionFactory
2438
{
39+
private static readonly Type[] internalFactorySignature = new[] {
40+
typeof(Expression), typeof(string), typeof(bool), typeof(IReadOnlyList<ParameterExpression>)
41+
};
42+
2543
private static readonly object _lock = new object();
2644
private static volatile LambdaExpressionFactory instance;
2745

@@ -32,27 +50,54 @@ public static LambdaExpressionFactory Instance {
3250
return instance;
3351
}
3452
}
35-
36-
private readonly MethodInfo factoryMethod;
53+
3754
private readonly ThreadSafeDictionary<Type, Factory> cache;
55+
private readonly Func<Type, Factory> createHandler;
56+
private readonly MethodInfo slowFactoryMethod;
3857

3958
public LambdaExpression CreateLambda(Type delegateType, Expression body, ParameterExpression[] parameters)
4059
{
41-
var factory = cache.GetValue(delegateType, (_delegateType, _this) => _this.CreateFactory(_delegateType), this);
60+
var factory = cache.GetValue(delegateType, createHandler);
4261
return factory.Invoke(body, parameters);
4362
}
44-
63+
4564
public LambdaExpression CreateLambda(Expression body, ParameterExpression[] parameters)
4665
{
4766
var delegateType = DelegateHelper.MakeDelegateType(body.Type, parameters.Select(p => p.Type));
4867
return CreateLambda(delegateType, body, parameters);
4968
}
5069

5170
#region Private / internal methods
52-
53-
private Factory CreateFactory(Type delegateType)
71+
72+
internal Factory CreateFactorySlow(Type delegateType)
73+
{
74+
var factory = (SlowFactory) Delegate.CreateDelegate(
75+
typeof(SlowFactory), slowFactoryMethod.MakeGenericMethod(delegateType));
76+
77+
return (body, parameters) => factory.Invoke(body, parameters);
78+
}
79+
80+
internal static Factory CreateFactoryFast(Type delegateType)
5481
{
55-
return (Factory) Delegate.CreateDelegate(typeof (Factory), factoryMethod.MakeGenericMethod(delegateType));
82+
var method = typeof(Expression<>).MakeGenericType(delegateType).GetMethod(
83+
"Create", BindingFlags.Static | BindingFlags.NonPublic, null, internalFactorySignature, null);
84+
85+
if (method == null) {
86+
return null;
87+
}
88+
89+
var factory = (FastFactory) Delegate.CreateDelegate(typeof(FastFactory), null, method);
90+
return (body, parameters) => factory.Invoke(body, null, false, parameters);
91+
}
92+
93+
internal static bool CanUseFastFactory()
94+
{
95+
try {
96+
return CreateFactoryFast(typeof(Func<int>)) != null;
97+
}
98+
catch {
99+
return false;
100+
}
56101
}
57102

58103
#endregion
@@ -62,11 +107,13 @@ private Factory CreateFactory(Type delegateType)
62107
private LambdaExpressionFactory()
63108
{
64109
cache = ThreadSafeDictionary<Type, Factory>.Create(new object());
65-
factoryMethod = typeof (Expression).GetMethods()
66-
.Where(m => m.IsGenericMethod
67-
&& m.Name == "Lambda"
68-
&& m.GetParameters()[1].ParameterType == typeof(IEnumerable<ParameterExpression>))
69-
.Single();
110+
111+
slowFactoryMethod = typeof(Expression).GetMethods().Single(m =>
112+
m.IsGenericMethod &&
113+
m.Name == "Lambda" &&
114+
m.GetParameters()[1].ParameterType == typeof(IEnumerable<ParameterExpression>));
115+
116+
createHandler = CanUseFastFactory() ? (Func<Type, Factory>) CreateFactoryFast : CreateFactorySlow;
70117
}
71118
}
72119
}

Orm/Xtensive.Orm/Orm/Building/Builders/PartialIndexFilterBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq.Expressions;
1111
using System.Reflection;
1212
using Xtensive.Core;
13+
using Xtensive.Linq;
1314
using Xtensive.Orm;
1415
using Xtensive.Reflection;
1516
using Xtensive.Orm.Linq;
@@ -37,7 +38,7 @@ public static void BuildFilter(IndexInfo index)
3738
var builder = new PartialIndexFilterBuilder(index, parameter);
3839
var body = builder.Visit(index.FilterExpression.Body);
3940
var filter = new PartialIndexFilterInfo {
40-
Expression = Expression.Lambda(body, parameter),
41+
Expression = FastExpression.Lambda(body, parameter),
4142
Fields = builder.usedFields,
4243
};
4344
index.Filter = filter;

Orm/Xtensive.Orm/Orm/Linq/ExpressionEvaluator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq.Expressions;
1111
using System.Reflection;
1212
using Xtensive.Core;
13+
using Xtensive.Linq;
1314
using Xtensive.Orm.Rse;
1415
using Xtensive.Reflection;
1516
using ExpressionVisitor = Xtensive.Linq.ExpressionVisitor;
@@ -50,7 +51,7 @@ public static ConstantExpression Evaluate(Expression e)
5051
Type type = e.Type;
5152
if (type.IsValueType)
5253
e = Expression.Convert(e, typeof (object));
53-
var lambda = Expression.Lambda<Func<object>>(e);
54+
var lambda = FastExpression.Lambda<Func<object>>(e);
5455
var func = lambda.CachingCompile();
5556
return Expression.Constant(func(), type);
5657
}

0 commit comments

Comments
 (0)