Considere la siguiente manipulación simple sobre una colección:
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);
Ahora usemos Expresiones. El siguiente código es aproximadamente equivalente:
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);
}
Pero quiero construir la expresión sobre la marcha, así que aquí hay una nueva prueba:
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);
}
Por supuesto que no es exactamente como el anterior, así que para ser justos, modifico ligeramente el primero:
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);
}
Ahora vienen los resultados para MAX = 100000, VS2008, depuración ON:
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
Y con la depuración desactivada:
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
Sorpresa . La expresión compilada es aproximadamente 17 veces más lenta que las otras alternativas. Ahora aquí vienen las preguntas:
- ¿Estoy comparando expresiones no equivalentes?
- ¿Existe un mecanismo para hacer que .NET "optimice" la expresión compilada?
- ¿Cómo expreso la misma llamada en cadena
l.Where(i => i % 2 == 0).Where(i => i > 5);
programáticamente?
Algunas estadísticas más. Visual Studio 2010, depuración activada, optimizaciones desactivadas:
Using lambda: 1093974
Using lambda compiled: 15315636
Using lambda combined: 781410
Depuración activada, optimizaciones activadas:
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
Depuración desactivada, optimizaciones activadas:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
Nueva sorpresa. El cambio de VS2008 (C # 3) a VS2010 (C # 4) hace que sea UsingLambdaCombined
más rápido que el lambda nativo.
Ok, encontré una manera de mejorar el rendimiento compilado de lambda en más de un orden de magnitud. Aquí tienes un consejo; después de ejecutar el generador de perfiles, el 92% del tiempo se dedica a:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
Hmmmm ... ¿Por qué está creando un nuevo delegado en cada iteración? No estoy seguro, pero la solución sigue en una publicación separada.
Stopwatch
para tiempos en lugar deDateTime.Now
.