考慮以下對集合的簡單操作:
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);
現在讓我們使用表達式。以下代碼大致相當:
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);
}
但是我想在運行中構建表達式,所以這是一個新的測試:
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,調試ON:
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
調試關閉:
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
驚喜編譯後的表達式比其他替代方案慢大約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
調試ON,優化ON:
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
調試OFF,優化ON:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
新驚喜。從VS2008(C#3)切換到VS2010(C#4),使UsingLambdaCombined
比原生lambda更快。
好吧,我找到了一種方法來將lambda編譯的性能提高一個數量級以上。這是一個提示;運行探查器後,92%的時間用於:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
嗯...為什麼在每次迭代中創建一個新的委託?我不確定,但解決方案是在一個單獨的帖子中。
可能是內部的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);
}
現在的時間是:
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,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
現在你可以爭辯說我沒有動態生成整個表達式;只是鏈接調用。但在上面的例子中,我生成了整個表達式。時間匹配。這只是編寫更少代碼的捷徑。
根據我的理解,正在發生的是.Compile()方法不會將編譯傳播到內部lambdas,因此不會將CreateDelegate
的常量調用傳播。但要真正理解這一點,我希望有一個.NET大師對內部事情的評論。
為什麼 , 為什麼現在比現在的lambda更快!?
最近我問了一個幾乎相同的問題:
我的解決方案是我不應該在Expression
上調用Compile
,但我應該在其上調用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"));
然而,這並不理想。我不太確定這適用於哪種類型,但我認為由委託作為參數或由委託返回的類型必須是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;
}
}
但就像我說的那樣,這並不理想,我仍然想知道為什麼編譯動態裝配的方法有時會快一個數量級。我有時會說,因為我也看到過使用Compile
的Expression
和普通方法一樣快的情況。看到我的問題。
或者如果有人知道繞過動態程序集的“無非public
類型”約束的方法,那也是受歡迎的。