컬렉션에 대한 다음과 같은 간단한 조작을 고려하십시오.
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 배 느립니다. 이제 질문이 있습니다.
- 동등하지 않은 표현을 비교하고 있습니까?
- .NET이 컴파일 된 표현식을 “최적화”하도록 만드는 메커니즘이 있습니까?
- 동일한 체인 호출을
l.Where(i => i % 2 == 0).Where(i => i > 5);
프로그래밍 방식으로 어떻게 표현 합니까?
더 많은 통계. Visual Studio 2010, 디버깅 켜기, 최적화 끄기 :
Using lambda: 1093974
Using lambda compiled: 15315636
Using lambda combined: 781410
디버깅 켜기, 최적화 켜기 :
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
디버깅 끄기, 최적화 켜기 :
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
새로운 놀라움. VS2008 (C # 3)에서 VS2010 (C # 4)으로 전환하면 UsingLambdaCombined
네이티브 람다보다 빠릅니다.
좋아, 나는 람다 컴파일 성능을 10 배 이상 향상시킬 수있는 방법을 찾았다. 팁 이요; 프로파일 러를 실행 한 후 92 %의 시간이 다음에 소비됩니다.
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
흠 … 모든 반복에서 새 델리게이트를 만드는 이유는 무엇입니까? 확실하지 않지만 솔루션은 별도의 게시물에 따릅니다.
답변
내부 람다가 컴파일되지 않을 수 있습니까?!? 개념 증명은 다음과 같습니다.
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
우와! 빠를뿐만 아니라 네이티브 람다보다 빠릅니다. ( 스크래치 헤드 ).
물론 위의 코드는 작성하기에 너무 고통 스럽습니다. 간단한 마술을 해보자.
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, 최적화 켜기, 디버깅 끄기 :
Using lambda: 781260
Using lambda compiled: 14687970
Using lambda combined: 468756
Using lambda compiled with MethodCall: 468756
Using lambda compiled constant: 468756
이제 내가 전체 표현식을 동적으로 생성하지 않는다고 주장 할 수 있습니다. 단지 연결 호출. 그러나 위의 예에서는 전체 표현식을 생성합니다. 그리고 타이밍이 일치합니다. 이것은 코드를 적게 작성하는 지름길 일뿐입니다.
내 이해에 따르면 .Compile () 메서드는 컴파일을 내부 람다로 전파하지 않으므로 상수 호출이 발생합니다. CreateDelegate
. 그러나 이것을 진정으로 이해하기 위해 .NET 전문가가 내부 작업에 대해 약간의 의견을 말하고 싶습니다.
그리고 왜 , 오 왜 이것이 이제 네이티브 람다보다 더 빠릅니다!?
답변
최근에 거의 동일한 질문을했습니다.
나를 위해이 솔루션은 내가 전화를 안이었다 Compile
온 Expression
,하지만 난 전화를해야 CompileToMethod
그것을하고 컴파일 Expression
A를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;
}
}
그러나 내가 말했듯이 이것은 이상적이지 않으며 방법을 동적 어셈블리로 컴파일하는 것이 때때로 훨씬 더 빠른 이유를 여전히 알고 싶습니다 . 나는 또한 경우에 본 적이 있기 때문에 가끔 말을 Expression
컴파일을Compile
이 일반적인 방법만큼 빠른 . 그것에 대한 내 질문을 참조하십시오.
또는 public
동적 어셈블리 에서 “no non- type”제약 조건 을 우회하는 방법을 알고있는 사람도 환영합니다.
답변
식이 동일하지 않으므로 왜곡 된 결과가 나타납니다. 나는 이것을 테스트하기 위해 테스트 벤치를 썼다. 테스트에는 정규 람다 호출, 동등한 컴파일 된 표현식, 손으로 만든 동등한 컴파일 된 표현식 및 구성된 버전이 포함됩니다. 더 정확한 숫자 여야합니다. 흥미롭게도, 나는 평범한 버전과 작곡 된 버전 사이에 많은 차이를 보지 못하고 있습니다. 그리고 컴파일 된 표현식은 자연스럽게 느리지 만 아주 조금만 느립니다. 좋은 숫자를 얻으려면 충분한 입력 및 반복 횟수가 필요합니다. 차이를 만듭니다.
두 번째 질문에 대해서는 어떻게하면 더 많은 성능을 얻을 수 있을지 모르기 때문에 거기에서 도움을 드릴 수 없습니다. 그것은 얻을 것만 큼 좋아 보인다.
HandMadeLambdaExpression()
방법 에서 세 번째 질문에 대한 내 대답을 찾을 수 있습니다. 확장 메서드로 인해 가장 쉬운 표현은 아니지만 가능합니다.
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 (ms) 람다 식 : 경과 : 357077202 129051 (ms) 평균 : 357.077202 0.129051 (ms) 손으로 만든 Lambda 표현 : 경과 : 345029281 124696 (ms) 평균 : 345.029281 0.124696 (ms) 구성 : 경과 : 340409238 123027 (ms) 평균 : 340.409238 0.123027 (ms) 구성된 표현 : 경과 : 350800599 126782 (ms) 평균 : 350.800599 0.126782 (ms) 손으로 만든 구성된 표현 : 경과 : 352811359 127509 (ms) 평균 : 352.811359 0.127509 (ms)
답변
런타임에 컴파일 된 코드는 최적화되지 않을 수 있지만 수동으로 작성하고 C # 컴파일러를 통해 컴파일 된 코드는 최적화되어 있기 때문에 대리자를 통해 컴파일 된 람다 성능이 느려질 수 있습니다.
둘째, 여러 람다 식은 여러 익명 메서드를 의미하며 각 메서드를 호출하면 직선 메서드를 평가하는 것보다 약간의 추가 시간이 걸립니다. 예를 들어
Console.WriteLine(x);
과
Action x => Console.WriteLine(x);
x(); // this means two different calls..
컴파일러의 관점에서 볼 때 두 번째로 약간 더 많은 오버 헤드가 필요하며 실제로 두 번의 다른 호출이 필요합니다. 먼저 x 자체를 호출 한 다음 해당 호출 x의 문 내에서 호출합니다.
따라서 결합 된 Lambda는 단일 람다 표현식에 비해 성능이 거의 저하되지 않습니다.
그리고 이것은 올바른 논리를 평가하고 있지만 컴파일러가 수행 할 추가 단계를 추가하고 있기 때문에 내부에서 실행되는 것과는 독립적입니다.
표현식 트리가 컴파일 된 후에도 최적화되지 않고 여전히 약간 복잡한 구조를 유지하며 평가하고 호출하면 컴파일 된 람다 표현식의 성능을 저하시킬 수있는 추가 유효성 검사, 널 검사 등이있을 수 있습니다.
답변
