编译C#Lambdaexpression式的性能
考虑以下对集合的简单操作:
static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var result = x.Where(i => i % 2 == 0).Where(i => i > 5);
现在让我们使用expression式。 以下代码大致相同:
static void UsingLambda() { Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5); var t0 = DateTime.Now.Ticks; for (int j = 1; j < MAX; j++) var sss = lambda(x).ToList(); var tn = DateTime.Now.Ticks; Console.WriteLine("Using lambda: {0}", tn - t0); }
但是我想立即构build这个expression式,所以这里有一个新的testing:
static void UsingCompiledExpression() { var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0)); var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5)); var argX = Expression.Parameter(typeof(IEnumerable<int>), "x"); var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX)); var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX); var c3 = f.Compile(); var t0 = DateTime.Now.Ticks; for (int j = 1; j < MAX; j++) var sss = c3(x).ToList(); var tn = DateTime.Now.Ticks; Console.WriteLine("Using lambda compiled: {0}", tn - t0); }
当然,这和上面不一样,所以为了公平起见,我稍微修改了第一个:
static void UsingLambdaCombined() { Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0); Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5); Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l)); var t0 = DateTime.Now.Ticks; for (int j = 1; j < MAX; j++) var sss = lambdaCombined(x).ToList(); var tn = DateTime.Now.Ticks; Console.WriteLine("Using lambda combined: {0}", tn - t0); }
现在来的结果是MAX = 100000,VS2008,debuggingON:
Using lambda compiled: 23437500 Using lambda: 1250000 Using lambda combined: 1406250
并在debuggingclosures:
Using lambda compiled: 21718750 Using lambda: 937500 Using lambda combined: 1093750
惊喜 。 编译后的expression式大约比其他替代方法慢17倍。 现在问题来了:
- 我比较非等价的expression式吗?
- 有没有一种机制,使.NET“优化”编译的expression式?
- 我如何expression相同的连锁
l.Where(i => i % 2 == 0).Where(i => i > 5);
编程?
更多的统计数据 Visual Studio 2010,debuggingON,优化OFF:
Using lambda: 1093974 Using lambda compiled: 15315636 Using lambda combined: 781410
debuggingON,优化ON:
Using lambda: 781305 Using lambda compiled: 15469839 Using lambda combined: 468783
debuggingOFF,优化ON:
Using lambda: 625020 Using lambda compiled: 14687970 Using lambda combined: 468765
新的惊喜。 从VS2008(C#3)切换到VS2010(C#4),使UsingLambdaCombined
比原生lambda更快。
好吧,我已经find了一种将lambda编译的性能提高一个数量级以上的方法。 这是一个提示; 运行分析器之后,92%的时间花费在:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
嗯…为什么它在每个迭代中创build一个新的委托? 我不确定,但解决scheme在一个单独的职位。
难道是内部lambda没有被编译?!? 这是一个概念的certificate:
static void UsingCompiledExpressionWithMethodCall() { var where = typeof(Enumerable).GetMember("Where").First() as System.Reflection.MethodInfo; where = where.MakeGenericMethod(typeof(int)); var l = Expression.Parameter(typeof(IEnumerable<int>), "l"); var arg0 = Expression.Parameter(typeof(int), "i"); var lambda0 = Expression.Lambda<Func<int, bool>>( Expression.Equal(Expression.Modulo(arg0, Expression.Constant(2)), Expression.Constant(0)), arg0).Compile(); var c1 = Expression.Call(where, l, Expression.Constant(lambda0)); var arg1 = Expression.Parameter(typeof(int), "i"); var lambda1 = Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(arg1, Expression.Constant(5)), arg1).Compile(); var c2 = Expression.Call(where, c1, Expression.Constant(lambda1)); var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(c2, l); var c3 = f.Compile(); var t0 = DateTime.Now.Ticks; for (int j = 1; j < MAX; j++) { var sss = c3(x).ToList(); } var tn = DateTime.Now.Ticks; Console.WriteLine("Using lambda compiled with MethodCall: {0}", tn - t0); }
而现在的时机是:
Using lambda: 625020 Using lambda compiled: 14687970 Using lambda combined: 468765 Using lambda compiled with MethodCall: 468765
活泉! 它不仅速度快,而且比本地的lambda快。 ( 刮头 )。
当然,上面的代码太难以写了。 让我们来做一些简单的魔术:
static void UsingCompiledConstantExpressions() { var f1 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i % 2 == 0)); var f2 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i > 5)); var argX = Expression.Parameter(typeof(IEnumerable<int>), "x"); var f3 = Expression.Invoke(Expression.Constant(f2), Expression.Invoke(Expression.Constant(f1), argX)); var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX); var c3 = f.Compile(); var t0 = DateTime.Now.Ticks; for (int j = 1; j < MAX; j++) { var sss = c3(x).ToList(); } var tn = DateTime.Now.Ticks; Console.WriteLine("Using lambda compiled constant: {0}", tn - t0); }
和一些时间,VS2010,优化ON,debuggingOFF:
Using lambda: 781260 Using lambda compiled: 14687970 Using lambda combined: 468756 Using lambda compiled with MethodCall: 468756 Using lambda compiled constant: 468756
现在你可以争辩说,我不是dynamic地生成整个expression式。 只是链接调用。 但是在上面的例子中,我生成了整个expression式。 和时间匹配。 这只是编写较less代码的捷径。
根据我的理解,发生的事情是.Compile()方法不会将编译内容传播到内部lambdaexpression式,因此不断的调用CreateDelegate
。 但要真正理解这一点,我很想有一个.NET大师对内部的东西进行一些评论。
为什么哦, 为什么现在比原生lambda更快?
最近我问了一个几乎相同的问题:
编译到委托expression式的性能
对我来说,解决scheme是我不应该在Expression
上调用Compile
,但是我应该调用CompileToMethod
并将该Expression
编译为dynamic程序集中的static
方法。
像这样:
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName("MyAssembly_" + Guid.NewGuid().ToString("N")), AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module"); var typeBuilder = moduleBuilder.DefineType("MyType_" + Guid.NewGuid().ToString("N"), TypeAttributes.Public)); var methodBuilder = typeBuilder.DefineMethod("MyMethod", MethodAttributes.Public | MethodAttributes.Static); expression.CompileToMethod(methodBuilder); var resultingType = typeBuilder.CreateType(); var function = Delegate.CreateDelegate(expression.Type, resultingType.GetMethod("MyMethod"));
然而,这并不理想。 我不太确定这种types适用于哪些types,但我认为由委托或由委托返回的参数types必须是public
和非generics的。 它必须是非generics的,因为genericstypes明显地访问System.__Canon
,这是.NET在genericstypes下使用的内部types,这违反了“必须是public
types规则”。
对于这些types,您可以使用显然较慢的Compile
。 我通过以下方式检测它们:
private static bool IsPublicType(Type t) { if ((!t.IsPublic && !t.IsNestedPublic) || t.IsGenericType) { return false; } int lastIndex = t.FullName.LastIndexOf('+'); if (lastIndex > 0) { var containgTypeName = t.FullName.Substring(0, lastIndex); var containingType = Type.GetType(containgTypeName + "," + t.Assembly); if (containingType != null) { return containingType.IsPublic; } return false; } else { return t.IsPublic; } }
但正如我所说,这不是理想的,我仍然想知道为什么编译dynamic汇编的方法有时快一个数量级。 我有时候也会说,因为我也看到了使用Compile
的Expression
和普通方法一样快的情况。 看到我的问题。
或者,如果有人知道绕过dynamic程序集的“非public
types”约束的方法,那也是很受欢迎的。
编译代码中的lambdaperformance可能会比较慢,因为编译的代码在运行时可能不会被优化,但是您手动编写的代码和通过C#编译器编译的代码已经过优化。
其次,多个lambdaexpression式意味着多个匿名方法,调用每个方法在计算直接方法上花费的时间不多。 例如,打电话
Console.WriteLine(x);
和
Action x => Console.WriteLine(x); x(); // this means two different calls..
是不同的,从编译器的angular度来看,第二个额外的开销是需要的,实际上是两个不同的调用。 首先调用x本身,然后在调用x的语句中。
所以你的组合Lambda肯定会比单个lambdaexpression式有一点缓慢的performance。
这与内部正在执行的内容无关,因为您仍然在评估正确的逻辑,但是您要为编译器添加额外的步骤来执行。
即使在编译expression式树之后,也不会有优化,并且仍然保留其小小的复杂结构,评估和调用它可能会有额外的validation,空检查等,这可能会减慢编译的lambdaexpression式的性能。
你的表情不相同,因此你会得到歪斜的结果。 我写了一个testing台来testing这个。 testing包括常规的lambda调用,等效的编译expression式,手工编写的等效编译expression式以及组合版本。 这些应该是更准确的数字。 有趣的是,我在平面和合成版本之间没有看到太多变化。 编译后的expression式自然会慢一些,但是只能很less。 你需要足够大的input和迭代计数来获得一些好的数字。 它有所作为。
至于你的第二个问题,我不知道你怎么能得到更多的performance,所以我不能帮你。 它看起来不错,它会得到。
你会在HandMadeLambdaExpression()
方法中find我对你的第三个问题的答案。 不是由于扩展方法而构build的最简单的expression式,而是可行的。
using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using System.Linq.Expressions; namespace ExpressionBench { class Program { static void Main(string[] args) { var values = Enumerable.Range(0, 5000); var lambda = GetLambda(); var lambdaExpression = GetLambdaExpression().Compile(); var handMadeLambdaExpression = GetHandMadeLambdaExpression().Compile(); var composed = GetComposed(); var composedExpression = GetComposedExpression().Compile(); var handMadeComposedExpression = GetHandMadeComposedExpression().Compile(); DoTest("Lambda", values, lambda); DoTest("Lambda Expression", values, lambdaExpression); DoTest("Hand Made Lambda Expression", values, handMadeLambdaExpression); Console.WriteLine(); DoTest("Composed", values, composed); DoTest("Composed Expression", values, composedExpression); DoTest("Hand Made Composed Expression", values, handMadeComposedExpression); } static void DoTest<TInput, TOutput>(string name, TInput sequence, Func<TInput, TOutput> operation, int count = 1000000) { for (int _ = 0; _ < 1000; _++) operation(sequence); var sw = Stopwatch.StartNew(); for (int _ = 0; _ < count; _++) operation(sequence); sw.Stop(); Console.WriteLine("{0}:", name); Console.WriteLine(" Elapsed: {0,10} {1,10} (ms)", sw.ElapsedTicks, sw.ElapsedMilliseconds); Console.WriteLine(" Average: {0,10} {1,10} (ms)", decimal.Divide(sw.ElapsedTicks, count), decimal.Divide(sw.ElapsedMilliseconds, count)); } static Func<IEnumerable<int>, IList<int>> GetLambda() { return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList(); } static Expression<Func<IEnumerable<int>, IList<int>>> GetLambdaExpression() { return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList(); } static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeLambdaExpression() { var enumerableMethods = typeof(Enumerable).GetMethods(); var whereMethod = enumerableMethods .Where(m => m.Name == "Where") .Select(m => m.MakeGenericMethod(typeof(int))) .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>)) .Single(); var toListMethod = enumerableMethods .Where(m => m.Name == "ToList") .Select(m => m.MakeGenericMethod(typeof(int))) .Single(); // helpers to create the static method call expressions Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression = (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param)); Func<Expression, Expression> ToListExpression = instance => Expression.Call(toListMethod, instance); //return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList(); var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v"); var expr0 = WhereExpression(exprParam, Expression.Parameter(typeof(int), "i"), i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0))); var expr1 = WhereExpression(expr0, Expression.Parameter(typeof(int), "i"), i => Expression.GreaterThan(i, Expression.Constant(5))); var exprBody = ToListExpression(expr1); return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam); } static Func<IEnumerable<int>, IList<int>> GetComposed() { Func<IEnumerable<int>, IEnumerable<int>> composed0 = v => v.Where(i => i % 2 == 0); Func<IEnumerable<int>, IEnumerable<int>> composed1 = v => v.Where(i => i > 5); Func<IEnumerable<int>, IList<int>> composed2 = v => v.ToList(); return v => composed2(composed1(composed0(v))); } static Expression<Func<IEnumerable<int>, IList<int>>> GetComposedExpression() { Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed0 = v => v.Where(i => i % 2 == 0); Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed1 = v => v.Where(i => i > 5); Expression<Func<IEnumerable<int>, IList<int>>> composed2 = v => v.ToList(); var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v"); var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam))); return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam); } static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeComposedExpression() { var enumerableMethods = typeof(Enumerable).GetMethods(); var whereMethod = enumerableMethods .Where(m => m.Name == "Where") .Select(m => m.MakeGenericMethod(typeof(int))) .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>)) .Single(); var toListMethod = enumerableMethods .Where(m => m.Name == "ToList") .Select(m => m.MakeGenericMethod(typeof(int))) .Single(); Func<ParameterExpression, Func<ParameterExpression, Expression>, Expression> LambdaExpression = (param, body) => Expression.Lambda(body(param), param); Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression = (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param)); Func<Expression, Expression> ToListExpression = instance => Expression.Call(toListMethod, instance); var composed0 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"), v => WhereExpression( v, Expression.Parameter(typeof(int), "i"), i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0)))); var composed1 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"), v => WhereExpression( v, Expression.Parameter(typeof(int), "i"), i => Expression.GreaterThan(i, Expression.Constant(5)))); var composed2 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"), v => ToListExpression(v)); var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v"); var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam))); return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam); } } }
结果在我的机器上:
LAMBDA: 已用时间:340971948 123230(ms) 平均:340.971948 0.12323(毫秒) Lambdaexpression式: 已用:357077202 129051(毫秒) 平均:357.077202 0.129051(毫秒) 手工制作Lambdaexpression式: 已用:345029281 124696(毫秒) 平均:345.029281 0.124696(毫秒) 组成: 已用:340409238 123027(毫秒) 平均:340.409238 0.123027(毫秒) 组合expression式: 已用:350800599 126782(毫秒) 平均:350.800599 0.126782(毫秒) 手工组成expression: 已用:352811359 127509(ms) 平均:352.811359 0.127509(毫秒)