Hug*_*ira 91 c# performance lambda expression-trees
考虑以下对集合的简单操作:
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);
Run Code Online (Sandbox Code Playgroud)
现在让我们使用表达式.以下代码大致相当:
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);
}
Run Code Online (Sandbox Code Playgroud)
但是我想在运行中构建表达式,所以这是一个新的测试:
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);
}
Run Code Online (Sandbox Code Playgroud)
当然它不完全像上面那样,所以公平地说,我稍微修改了第一个:
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);
}
Run Code Online (Sandbox Code Playgroud)
现在结果为MAX = 100000,VS2008,调试ON:
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
Run Code Online (Sandbox Code Playgroud)
调试关闭:
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
Run Code Online (Sandbox Code Playgroud)
惊喜.编译后的表达式比其他替代方案慢大约17倍.现在问题来了:
l.Where(i => i % 2 == 0).Where(i => i > 5);
编程方式表达相同的链式调用?更多统计数据.Visual Studio 2010,调试ON,优化OFF:
Using lambda: 1093974
Using lambda compiled: 15315636
Using lambda combined: 781410
Run Code Online (Sandbox Code Playgroud)
调试ON,优化ON:
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
Run Code Online (Sandbox Code Playgroud)
调试OFF,优化ON:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
Run Code Online (Sandbox Code Playgroud)
新惊喜.从VS2008(C#3)切换到VS2010(C#4),UsingLambdaCombined
比原始lambda更快.
好的,我找到了一种方法来将lambda编译的性能提高一个数量级以上.这是一个提示; 运行探查器后,92%的时间用于:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
Run Code Online (Sandbox Code Playgroud)
嗯...为什么在每次迭代中都会创建一个新的委托?我不确定,但解决方案是在一个单独的帖子中.
Hug*_*ira 43
可能是内部的lambda没有编译?!?这是一个概念证明:
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);
}
Run Code Online (Sandbox Code Playgroud)
现在的时间是:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
Using lambda compiled with MethodCall: 468765
Run Code Online (Sandbox Code Playgroud)
活泉!它不仅速度快,而且比原生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);
}
Run Code Online (Sandbox Code Playgroud)
还有一些时序,VS2010,Optimizations ON,Debugging OFF:
Using lambda: 781260
Using lambda compiled: 14687970
Using lambda combined: 468756
Using lambda compiled with MethodCall: 468756
Using lambda compiled constant: 468756
Run Code Online (Sandbox Code Playgroud)
现在你可以争辩说我没有动态生成整个表达式; 只是链接调用.但在上面的例子中,我生成了整个表达式.时间匹配.这只是编写更少代码的捷径.
根据我的理解,正在发生的是.Compile()方法不会将编译传播到内部lambdas,因此不会将常量调用传播到CreateDelegate
.但要真正理解这一点,我希望有一个.NET专家对内部事情进行一些评论.
而且为什么,哦,为什么是这样的,现在比原来的拉姆达快!?
Jul*_*anR 10
最近我问了一个几乎相同的问题:
对我来说,解决方案是我不应该叫Compile
的Expression
,但我应该叫CompileToMethod
上它和编译Expression
的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"));
Run Code Online (Sandbox Code Playgroud)
然而,这并不理想.我不是很肯定这适用于哪些类型的准确,但我认为这是作为参数通过委托,或返回由委托类型必须是public
和非通用.它必须是非泛型的,因为泛型类型显然是访问System.__Canon
,这是.NET在泛型类型下使用的内部类型,这违反了"必须是public
类型规则".
对于那些类型,你可以使用明显更慢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;
}
}
Run Code Online (Sandbox Code Playgroud)
但就像我说的那样,这并不理想,我仍然想知道为什么编译动态装配的方法有时会快一个数量级.我有时会说,因为我也看到过Expression
编译的情况与Compile
普通方法一样快的情况.看到我的问题.
或者如果有人知道绕过public
动态组件的"无非类型"约束的方法,那也是受欢迎的.
归档时间: |
|
查看次数: |
31079 次 |
最近记录: |