-
Notifications
You must be signed in to change notification settings - Fork 934
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid lambda compilation as much as possible #2957
Avoid lambda compilation as much as possible #2957
Conversation
master update
Benchmark results: Expression private static Expression GetExpression()
{
(int, int) param = (1, 1);
(DateTime, DateTime) date = (DateTime.Now, DateTime.Now);
Expression<Func<string>> e = () => param.Item1.ToString() + Convert.ToInt32(date.Item2.ToString("yyyyMMdd"));
return e.Body;
} BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19042.1348 (20H2/October2020Update)
// * Hints * // * Legends * Sourceusing BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Linq.Expressions;
using System.Reflection;
namespace MyBenchmark
{
public class MyBenchmark
{
public static object FindValueOld(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant)
return ((ConstantExpression)expression).Value;
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberAccess = (MemberExpression)expression;
if (memberAccess.Expression == null || memberAccess.Expression.NodeType == ExpressionType.Constant)
{
var constantValue = ((ConstantExpression)memberAccess.Expression)?.Value;
var member = memberAccess.Member;
switch (member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)member).GetValue(constantValue);
case MemberTypes.Property:
return ((PropertyInfo)member).GetValue(constantValue);
}
}
}
var valueExpression = Expression.Lambda(expression).Compile();
object value = valueExpression.DynamicInvoke();
return value;
}
public static object FindValue(Expression expression)
{
object value;
object[] args;
Type type;
switch (expression.NodeType)
{
case ExpressionType.Constant:
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)expression;
value = memberExpression.Expression != null ? FindValue(memberExpression.Expression) : null;
switch (memberExpression.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberExpression.Member).GetValue(value);
case MemberTypes.Property:
return ((PropertyInfo)memberExpression.Member).GetValue(value);
}
break;
case ExpressionType.Convert:
case ExpressionType.Not:
case ExpressionType.Negate:
case ExpressionType.UnaryPlus:
case ExpressionType.Decrement:
case ExpressionType.Increment:
var unaryExpression = (UnaryExpression)expression;
value = FindValue(unaryExpression.Operand);
switch (expression.NodeType)
{
case ExpressionType.Convert:
type = Nullable.GetUnderlyingType(unaryExpression.Type) ?? unaryExpression.Type;
if (type == typeof(object) || value == null)
{
return value;
}
else if (value is IConvertible || unaryExpression.Method != null)
{
if (type != unaryExpression.Operand.Type)
{
value = unaryExpression.Method != null ? unaryExpression.Method.Invoke(null, new[] { value }) : Convert.ChangeType(value, type);
}
return value;
}
break;
case ExpressionType.Not:
if (value is bool)
{
return !(dynamic)value;
}
else
{
return ~(dynamic)value;
}
case ExpressionType.Negate:
return -(dynamic)value;
case ExpressionType.UnaryPlus:
return +(dynamic)value;
case ExpressionType.Decrement:
return (dynamic)value-1;
case ExpressionType.Increment:
return (dynamic)value+1;
}
break;
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)expression;
args = new object[methodCallExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
args[i] = FindValue(methodCallExpression.Arguments[i]);
if (methodCallExpression.Object == null) //extension or static method
{
if (args.Length > 0 && args[0] != null)
{
return methodCallExpression.Method.Invoke(args[0].GetType(), args);
}
else
{
return methodCallExpression.Method.Invoke(methodCallExpression.Method.DeclaringType, args);
}
}
else
{
var callingObject = FindValue(methodCallExpression.Object);
return methodCallExpression.Method.Invoke(callingObject, args);
}
case ExpressionType.Multiply:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.Add:
case ExpressionType.Subtract:
case ExpressionType.Power:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.ExclusiveOr:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.ArrayIndex:
case ExpressionType.Coalesce:
case ExpressionType.LeftShift:
case ExpressionType.RightShift:
var binaryExpression = (BinaryExpression)expression;
dynamic left = FindValue(binaryExpression.Left);
dynamic right = FindValue(binaryExpression.Right);
if (binaryExpression.Method != null)
{
return binaryExpression.Method.Invoke(null, new[] { left, right });
}
else
{
switch (expression.NodeType)
{
case ExpressionType.Multiply:
return left * right;
case ExpressionType.Divide:
return left / right;
case ExpressionType.Modulo:
return left % right;
case ExpressionType.Add:
return left + right;
case ExpressionType.Subtract:
return left - right;
case ExpressionType.Power:
return left ^ right;
case ExpressionType.AndAlso:
return left && right;
case ExpressionType.OrElse:
return left || right;
case ExpressionType.And:
return left & right;
case ExpressionType.Or:
return left | right;
case ExpressionType.ExclusiveOr:
return left ^ right;
case ExpressionType.Equal:
return left == right;
case ExpressionType.NotEqual:
return left != right;
case ExpressionType.LessThan:
return left < right;
case ExpressionType.LessThanOrEqual:
return left <= right;
case ExpressionType.GreaterThan:
return left > right;
case ExpressionType.GreaterThanOrEqual:
return left >= right;
case ExpressionType.ArrayIndex:
return left[right];
case ExpressionType.Coalesce:
return left ?? right;
case ExpressionType.LeftShift:
return left << right;
case ExpressionType.RightShift:
return left >> right;
}
}
break;
case ExpressionType.Conditional:
var conditionalExpression = (ConditionalExpression)expression;
bool condition = (bool)FindValue(conditionalExpression.Test);
if (condition)
{
return FindValue(conditionalExpression.IfTrue);
}
else
{
return FindValue(conditionalExpression.IfFalse);
}
case ExpressionType.NewArrayInit:
var newArrayExpression = (NewArrayExpression)expression;
type = newArrayExpression.Type.GetElementType();
if (type != null)
{
dynamic array = Array.CreateInstance(type, newArrayExpression.Expressions.Count);
for (int i = 0; i< newArrayExpression.Expressions.Count; i++)
{
array[i] = (dynamic)FindValue(newArrayExpression.Expressions[i]);
}
return array;
}
break;
case ExpressionType.ListInit:
var listInitExpression = (ListInitExpression)expression;
var list = Activator.CreateInstance(listInitExpression.Type);
foreach (var item in listInitExpression.Initializers)
{
args = new object[item.Arguments.Count];
for (int i = 0; i < args.Length; i++)
{
args[i] = FindValue(item.Arguments[i]);
}
item.AddMethod.Invoke(list, args);
}
return list;
case ExpressionType.New:
var newExpression = (NewExpression)expression;
args = new object[newExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
{
args[i] = FindValue(newExpression.Arguments[i]);
}
return newExpression.Constructor.Invoke(args);
}
return Expression.Lambda(expression).Compile().DynamicInvoke();
}
private static Expression GetExpression()
{
(int, int) param = (1, 1);
(DateTime, DateTime) date = (DateTime.Now, DateTime.Now);
Expression<Func<object>> e = () => param.Item1.ToString() + Convert.ToInt32(date.Item2.ToString("yyyyMMdd"));
return e.Body;
}
private static Expression GetExpression2()
{
Expression<Func<string>> e = () => new int[] { 1, 2 }.First().ToString();
return e.Body;
}
private static Expression GetExpression3()
{
Expression<Func<List<int>>> e = () => new List<int>() { 1, 2 };
return e.Body;
}
private static Expression GetExpression4()
{
int[] abc = new int[] { 1 };
Expression<Func<int>> e = () => abc[0];
return e.Body;
}
private static Expression GetExpression5()
{
Expression<Func<int>> e = () => new { Abcd = 5 }.Abcd;
return e.Body;
}
private static Expression GetExpression6()
{
Random random = new();
Expression<Func<bool>> e = () => !(random.Next(1, 3)==random.Next(1, 5));
return e.Body;
}
private static Expression GetExpression7()
{
int a = 5;
Expression<Func<int>> e = () => (-a);
return e.Body;
}
private static Expression GetExpression8()
{
int a = 5;
Expression<Func<int>> e = () => (a+1);
return e.Body;
}
[Benchmark(Baseline = true)]
public object FindValueOld()
{
return FindValueOld(GetExpression());
}
[Benchmark]
public object FindValue()
{
return FindValue(GetExpression());
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<MyBenchmark>();
}
}
//public class Program
//{
// public static void Main(string[] args)
// {
// MyBenchmark myBenchmark = new MyBenchmark();
// for (int i = 0; i < 1000000; i++)
// {
// myBenchmark.FindValue();
// }
// System.Console.WriteLine("The end");
// System.Console.ReadLine();
// }
//}
} |
This comment has been minimized.
This comment has been minimized.
I'm not sure we should handle so many cases. If you really need perf - you should avoid calculations inside query expression (so you should move all calculations to variable outside of query) For instance try your benchmark with the following modifications in Sourceprivate static Expression GetExpressionCalcInParam()
{
(int, int) param = (1, 1);
(DateTime, DateTime) date = (DateTime.Now, DateTime.Now);
var result = param.Item1.ToString() + Convert.ToInt32(date.Item2.ToString("yyyyMMdd"));
Expression<Func<object>> e = () => result;
return e.Body;
}
[Benchmark(Baseline = true)]
public object FindValueOld()
{
return FindValueOld(GetExpressionCalcInParam());
}
[Benchmark]
public object FindValue()
{
return FindValue(GetExpression());
} I'm pretty sure it will be 2+ times faster than your version. So I would add support for Convert (as it's easy to miss implicit cast) and maybe Method call and that's it. But let's hear what other members think. |
Hi @bahusoid, We should avoid lambda compilation as much as possible which is the current production performance issue for us. Queries do not include calculations or other controls does not affected this code. GC(suspencions) is a big issue, if you check below usages of We can handle all of above so why not, we can support as you wish, please consider it. Finally you are wright, If we move them from expression it will be faster but if linq lets us to write this and if its causes GC(suspensions) we need to fix it as much as possible. |
@razzmatazz could you test with this version, you have reported same issues recently |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Hi @bahusoid, Compile(true)
Tested with private static Expression GetExpression()
{
Expression<Func<int>> e = () => Convert.ToInt32(DateTime.Now.ToString("yyyyMMdd"));
return e.Body;
} Sourceusing BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Linq.Expressions;
using System.Reflection;
namespace MyBenchmark
{
public class MyBenchmark
{
public static object FindValueOld(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant)
return ((ConstantExpression)expression).Value;
if (expression.NodeType == ExpressionType.MemberAccess)
{
var memberAccess = (MemberExpression)expression;
if (memberAccess.Expression == null || memberAccess.Expression.NodeType == ExpressionType.Constant)
{
var constantValue = ((ConstantExpression)memberAccess.Expression)?.Value;
var member = memberAccess.Member;
switch (member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)member).GetValue(constantValue);
case MemberTypes.Property:
return ((PropertyInfo)member).GetValue(constantValue);
}
}
}
return Expression.Lambda(expression).Compile().DynamicInvoke();
}
public static object FindValue(Expression expression)
{
object value;
object[] args;
Type type;
switch (expression.NodeType)
{
case ExpressionType.Constant:
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)expression;
value = memberExpression.Expression != null ? FindValue(memberExpression.Expression) : null;
switch (memberExpression.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberExpression.Member).GetValue(value);
case MemberTypes.Property:
return ((PropertyInfo)memberExpression.Member).GetValue(value);
}
break;
case ExpressionType.Convert:
case ExpressionType.Not:
case ExpressionType.Negate:
case ExpressionType.UnaryPlus:
case ExpressionType.Decrement:
case ExpressionType.Increment:
var unaryExpression = (UnaryExpression)expression;
value = FindValue(unaryExpression.Operand);
switch (expression.NodeType)
{
case ExpressionType.Convert:
type = Nullable.GetUnderlyingType(unaryExpression.Type) ?? unaryExpression.Type;
if (type == typeof(object) || value == null)
{
return value;
}
else if (value is IConvertible || unaryExpression.Method != null)
{
if (type != unaryExpression.Operand.Type)
{
value = unaryExpression.Method != null ? unaryExpression.Method.Invoke(null, new[] { value }) : Convert.ChangeType(value, type);
}
return value;
}
break;
case ExpressionType.Not:
if (value is bool)
{
return !(dynamic)value;
}
else
{
return ~(dynamic)value;
}
case ExpressionType.Negate:
return -(dynamic)value;
case ExpressionType.UnaryPlus:
return +(dynamic)value;
case ExpressionType.Decrement:
return (dynamic)value-1;
case ExpressionType.Increment:
return (dynamic)value+1;
}
break;
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)expression;
args = new object[methodCallExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
args[i] = FindValue(methodCallExpression.Arguments[i]);
if (methodCallExpression.Object == null) //extension or static method
{
if (args.Length > 0 && args[0] != null)
{
return methodCallExpression.Method.Invoke(args[0].GetType(), args);
}
else
{
return methodCallExpression.Method.Invoke(methodCallExpression.Method.DeclaringType, args);
}
}
else
{
var callingObject = FindValue(methodCallExpression.Object);
return methodCallExpression.Method.Invoke(callingObject, args);
}
case ExpressionType.Multiply:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.Add:
case ExpressionType.Subtract:
case ExpressionType.Power:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.ExclusiveOr:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.ArrayIndex:
case ExpressionType.Coalesce:
case ExpressionType.LeftShift:
case ExpressionType.RightShift:
var binaryExpression = (BinaryExpression)expression;
dynamic left = FindValue(binaryExpression.Left);
dynamic right = FindValue(binaryExpression.Right);
if (binaryExpression.Method != null)
{
return binaryExpression.Method.Invoke(null, new[] { left, right });
}
else
{
switch (expression.NodeType)
{
case ExpressionType.Multiply:
return left * right;
case ExpressionType.Divide:
return left / right;
case ExpressionType.Modulo:
return left % right;
case ExpressionType.Add:
return left + right;
case ExpressionType.Subtract:
return left - right;
case ExpressionType.Power:
return left ^ right;
case ExpressionType.AndAlso:
return left && right;
case ExpressionType.OrElse:
return left || right;
case ExpressionType.And:
return left & right;
case ExpressionType.Or:
return left | right;
case ExpressionType.ExclusiveOr:
return left ^ right;
case ExpressionType.Equal:
return left == right;
case ExpressionType.NotEqual:
return left != right;
case ExpressionType.LessThan:
return left < right;
case ExpressionType.LessThanOrEqual:
return left <= right;
case ExpressionType.GreaterThan:
return left > right;
case ExpressionType.GreaterThanOrEqual:
return left >= right;
case ExpressionType.ArrayIndex:
return left[right];
case ExpressionType.Coalesce:
return left ?? right;
case ExpressionType.LeftShift:
return left << right;
case ExpressionType.RightShift:
return left >> right;
}
}
break;
case ExpressionType.Conditional:
var conditionalExpression = (ConditionalExpression)expression;
bool condition = (bool)FindValue(conditionalExpression.Test);
if (condition)
{
return FindValue(conditionalExpression.IfTrue);
}
else
{
return FindValue(conditionalExpression.IfFalse);
}
case ExpressionType.NewArrayInit:
var newArrayExpression = (NewArrayExpression)expression;
type = newArrayExpression.Type.GetElementType();
if (type != null)
{
dynamic array = Array.CreateInstance(type, newArrayExpression.Expressions.Count);
for (int i = 0; i< newArrayExpression.Expressions.Count; i++)
{
array[i] = (dynamic)FindValue(newArrayExpression.Expressions[i]);
}
return array;
}
break;
case ExpressionType.ListInit:
var listInitExpression = (ListInitExpression)expression;
var list = Activator.CreateInstance(listInitExpression.Type);
foreach (var item in listInitExpression.Initializers)
{
args = new object[item.Arguments.Count];
for (int i = 0; i < args.Length; i++)
{
args[i] = FindValue(item.Arguments[i]);
}
item.AddMethod.Invoke(list, args);
}
return list;
case ExpressionType.New:
var newExpression = (NewExpression)expression;
args = new object[newExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
{
args[i] = FindValue(newExpression.Arguments[i]);
}
return newExpression.Constructor.Invoke(args);
}
return Expression.Lambda(expression).Compile().DynamicInvoke();
}
public static object FindValueV2(Expression expression)
{
object value;
switch (expression.NodeType)
{
case ExpressionType.Constant:
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)expression;
value = memberExpression.Expression != null ? FindValueV2(memberExpression.Expression) : null;
switch (memberExpression.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberExpression.Member).GetValue(value);
case MemberTypes.Property:
return ((PropertyInfo)memberExpression.Member).GetValue(value);
}
break;
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)expression;
var args = new object[methodCallExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
args[i] = FindValueV2(methodCallExpression.Arguments[i]);
if (methodCallExpression.Object == null) //extension or static method
{
if (args.Length > 0 && args[0] != null)
{
return methodCallExpression.Method.Invoke(args[0].GetType(), args);
}
else
{
return methodCallExpression.Method.Invoke(methodCallExpression.Method.DeclaringType, args);
}
}
else
{
var callingObject = FindValueV2(methodCallExpression.Object);
return methodCallExpression.Method.Invoke(callingObject, args);
}
case ExpressionType.Convert:
var unaryExpression = (UnaryExpression)expression;
value = FindValueV2(unaryExpression.Operand);
var type = Nullable.GetUnderlyingType(unaryExpression.Type) ?? unaryExpression.Type;
if (type == typeof(object) || value == null)
{
return value;
}
else if (value is IConvertible || unaryExpression.Method != null)
{
if (type != unaryExpression.Operand.Type)
{
value = unaryExpression.Method != null ? unaryExpression.Method.Invoke(null, new[] { value }) : Convert.ChangeType(value, type);
}
return value;
}
break;
case ExpressionType.Multiply:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.Add:
case ExpressionType.Subtract:
case ExpressionType.Power:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.ArrayIndex:
case ExpressionType.Coalesce:
var binaryExpression = (BinaryExpression)expression;
dynamic left = FindValueV2(binaryExpression.Left);
dynamic right = FindValueV2(binaryExpression.Right);
if (binaryExpression.Method != null)
{
return binaryExpression.Method.Invoke(null, new[] { left, right });
}
else
{
switch (expression.NodeType)
{
case ExpressionType.Multiply:
return left * right;
case ExpressionType.Divide:
return left / right;
case ExpressionType.Modulo:
return left % right;
case ExpressionType.Add:
return left + right;
case ExpressionType.Subtract:
return left - right;
case ExpressionType.Power:
return left ^ right;
case ExpressionType.AndAlso:
return left && right;
case ExpressionType.OrElse:
return left || right;
case ExpressionType.And:
return left & right;
case ExpressionType.Or:
return left | right;
case ExpressionType.Equal:
return left == right;
case ExpressionType.NotEqual:
return left != right;
case ExpressionType.LessThan:
return left < right;
case ExpressionType.LessThanOrEqual:
return left <= right;
case ExpressionType.GreaterThan:
return left > right;
case ExpressionType.GreaterThanOrEqual:
return left >= right;
case ExpressionType.ArrayIndex:
return left[right];
case ExpressionType.Coalesce:
return left ?? right;
}
}
break;
}
return Expression.Lambda(expression).Compile(true).DynamicInvoke();
}
public static object FindValueV3(Expression expression)
{
object value;
switch (expression.NodeType)
{
case ExpressionType.Constant:
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression)expression;
value = memberExpression.Expression != null ? FindValueV3(memberExpression.Expression) : null;
switch (memberExpression.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberExpression.Member).GetValue(value);
case MemberTypes.Property:
return ((PropertyInfo)memberExpression.Member).GetValue(value);
}
break;
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression)expression;
var args = new object[methodCallExpression.Arguments.Count];
for (int i = 0; i < args.Length; i++)
args[i] = FindValueV3(methodCallExpression.Arguments[i]);
if (methodCallExpression.Object == null) //extension or static method
{
if (args.Length > 0 && args[0] != null)
{
return methodCallExpression.Method.Invoke(args[0].GetType(), args);
}
else
{
return methodCallExpression.Method.Invoke(methodCallExpression.Method.DeclaringType, args);
}
}
else
{
var callingObject = FindValueV3(methodCallExpression.Object);
return methodCallExpression.Method.Invoke(callingObject, args);
}
case ExpressionType.Convert:
var unaryExpression = (UnaryExpression)expression;
value = FindValueV3(unaryExpression.Operand);
var type = Nullable.GetUnderlyingType(unaryExpression.Type) ?? unaryExpression.Type;
if (type == typeof(object) || value == null)
{
return value;
}
else if (value is IConvertible || unaryExpression.Method != null)
{
if (type != unaryExpression.Operand.Type)
{
value = unaryExpression.Method != null ? unaryExpression.Method.Invoke(null, new[] { value }) : Convert.ChangeType(value, type);
}
return value;
}
break;
}
return Expression.Lambda(expression).Compile(true).DynamicInvoke();
}
private static Expression GetExpression()
{
Expression<Func<int>> e = () => Convert.ToInt32(DateTime.Now.ToString("yyyyMMdd"));
return e.Body;
}
private static Expression GetExpressionCalcInParam()
{
(int, int) param = (1, 1);
(DateTime, DateTime) date = (DateTime.Now, DateTime.Now);
var result = param.Item1.ToString() + Convert.ToInt32(date.Item2.ToString("yyyyMMdd"));
Expression<Func<object>> e = () => result;
return e.Body;
}
[Benchmark(Baseline = true)]
public object FindValueOld()
{
return FindValueOld(GetExpression());
}
[Benchmark]
public object FindValue()
{
return FindValue(GetExpression());
}
[Benchmark]
public object FindValueV2()
{
return FindValueV2(GetExpression());
}
[Benchmark]
public object FindValueV3()
{
return FindValueV3(GetExpression());
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<MyBenchmark>();
}
}
//public class Program
//{
// public static void Main(string[] args)
// {
// MyBenchmark myBenchmark = new MyBenchmark();
// for (int i = 0; i < 5000000; i++)
// {
// myBenchmark.FindValueV3();
// }
// System.Console.WriteLine("The end");
// System.Console.ReadLine();
// }
//}
} |
Good finding! Yeah sure we should use it. But does it make any sense to add anything at all to existing code except var valueExpression = Expression.Lambda(expression)
#if NETCOREAPP2_0_OR_GREATER || NET47_OR_GREATER || NETSTANDARD2_0_OR_GREATER
.Compile(true);
#else
.Compile();
#endif
object value = valueExpression.DynamicInvoke();
return value; |
Thank you, existing code is too limited, if you ok with that I would like to use FindValu3 has capable wihout compilation with below usages: |
OK |
I will look at it later. But you would need to add tests for it (implicit conversion int -> long, valid up convert - Entity -> SubEntity, invalid convert like ((string)(object)(int)1)), At a quick glance: I don't like |
Me too but these test has been failed without IConvertible, dou you have other idea? https://teamcity.jetbrains.com/viewLog.html?buildId=3720513&buildTypeId=bt7 If you help me a little bit(such as simple test) I will add additinoal tests in it. |
var valueExpression = Expression.Lambda(expression).Compile(); | ||
object value = valueExpression.DynamicInvoke(); | ||
var valueExpression = Expression.Lambda(expression) | ||
#if NETCOREAPP2_0_OR_GREATER || NET47_OR_GREATER || NETSTANDARD2_0_OR_GREATER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create an extension method instead:
#if NET461
internal static class LambdaExpressionExtensions
{
public Delegate Compile (this LambdaExpression expression, bool preferInterpretation) =>
expression.Compile();
}
#endif
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means a net47-48 application cannot benefit from this optimization until we upgrade the NHibernate target to net47, right? (Or add a target for net48 by example.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think community needs to discuss, to apply this fix to older versions of nhibernate for fixing GC issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fredericDelaporte it is essentially the same code just shifted to an extension method to reduce noise. I did not do any thinking beyond that. We probably can build a runtime shim to discover the underlying method (as we do for other stuff).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are going to add net60 target to 5.4 than why not also add net47 (or net48 but seems net48 doesn't add anything useful for us). I would prefer to add target rather than adding such shims.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MS recommends adding 4.8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems preferInterpretation is a lie under .NET Framework:
https://github.com/microsoft/referencesource/blob/e47d8c1b6482e0babe243dff2f9ece1507c814ff/System.Core/Microsoft/Scripting/Ast/LambdaExpression.cs#L147-L150
…ll causes "Garbage Collector" suspend all threads too often
fa355ff
to
f1586a9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Convert
is a tricky one - we definitely need tests. Added some comments below but think yourself for other cases and write test cases...
You can add separate test fixture like ExpressionProcessorFindValueFixture
put it near ExpressionProcessorFixture
. And tests could look something like:
private static object GetValue<T>(Expression<Func<T>> expression)
{
try
{
return ExpressionProcessor.FindValue(expression.Body);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}
[Test]
public void SomeInvalidConversions()
{
object val = 1;
Assert.Throws<InvalidCastException>(() =>
GetValue(() => (string)val));
}
Co-authored-by: Alex Zaytsev <alexzaytsev2019@gmail.com>
…o op in NetFX This reverts commit 5c8ad16.
@bahusoid first of all thank you for your guidence, feels nice to contribute. Shall we expect a merge to master this week? |
src/NHibernate.Test/Criteria/Lambda/ExpressionProcessorFindValueFixture.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
[Test] | ||
public void Int16ToIntegerImplicitCast() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would fall into 'lambda compile' path. Do we want to fix it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have tried to fix it bu convert is to much tricky dou you have any idea to fix it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said in #2957 (comment)
Let's start from something simple but safe and make it more complex and sophisticated in other PRs. Unless you already have in mind some simple fix that you want to stick in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless you already have in mind some simple fix
It is a variation of ConvertType
in the expression processor. Convert for primitive types (IsPrimitive
):
The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a variation of ConvertType
I would do it in another PR. It doesn't look as straightforward fix to me. For instance Boolean type and value overflows might behave differently in runtime vs ChangeType.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @bahusoid, in my test I was stuck with NestedPropertyToIntegerCast
and StringToIntegerCastFails
failed with Convert.ChangeType
Such as
Convert.ChangeType(9.89m, typeof(int)) = 10, (int)9.89m = 9
Convert.ChangeType(true, typeof(int)) = 1, (int)true = fails
I also tried with T Cast<T>(object value) => return (T)value;
store static instance of CastMethodInfo and make generic method before invoke but this was also failed. Which may run for primitive cases, for performance i am not sure of it? @hazzik
@bahusoid @hazzik @fredericDelaporte what dou you think about merging this pull request to master, if you need any other test or improvment I can do it. |
I think it's good to go.. @hazzik Any objections? |
I'm not really happy with the direction where this PR is going. But I dont have any objections against merging the PR. |
Why are u not really happy @hazzik? It has been a very enjoyable work for me and I am very happy to work with you. Thank you. |
Something feels wrong about the approach here, however I could not properly articulate what is wrong and how to make it right. I think this probably should to be done somewhere else. Perhaps, together at the stage where the independent sub-trees are identified. Also, this implementation, in some circumstances could lead to additional compilations of lambdas instead of suppressing them. Anyway, just for clarification: I don't have any objections against merging this PR as is. And there is certainly nothing personal. |
Thanks @hazzik, this seems to be the only way to prevent lambda compilations that I can find for now. We look forward to merge of this pull request :) |
Just for good measure, I tested two other approaches.
I believe the best solution is a combination of approaches, but for now the reflection approach is, somewhat surprisingly, the fastest. |
All linq dml style(update/delete) and select queries take times for compilation issue, with Dynatrace monitoring we can see elapsed time(self time) in
ExpressionProcessor
is too much depending of your query.When I open #2952 I tell about suspensions because of expression compile every time causes gc pressure.
For performance point of view I think we need to avoid expression compile as much as possible.
For queries those are not cacheable(query plan cache) we need to create warning log, because this kind of performance issue is not easy to find out.
Below image shows single query calls 6 times

ExpressionProcessor.FindValue
, just 2 of them callsExpression.Lambda(expression).Compile().DynamicInvoke()
and takes 5.49msFor details see #2952 (comment)
Continuation of #2948
https://particular.net/blog/10x-faster-execution-with-compiled-expression-trees
Expression compile causes too much GC call, application suspensions as a result. To see same picture on Visual Studio
MyBenchmark.Main
callFindValueOld
in loop.