I would have expected delegates generated from expression trees to achieve roughly the same performance as hard-coded, static, equivalent anonymous methods. However, it seems that dynamically generated delegates are noticeably slower...
Here's a simple test program to illustrate the case. It just accesses 3 properties of an object 1000000 times:
static void Main()
{
var foo = new Foo { A = 42, B = "Hello world", C = new DateTime(1970, 1, 1) };
Func<Foo, int> getA;
Func<Foo, string> getB;
Func<Foo, DateTime> getC;
// Using hard-coded lambdas
getA = f => f.A;
getB = f => f.B;
getC = f => f.C;
Console.WriteLine("Hard-coded: {0}", Test(foo, getA, getB, getC));
// Using dynamically generated delegates
ParameterExpression prm = Expression.Parameter(typeof(Foo), "foo");
getA = Expression.Lambda<Func<Foo, int>>(Expression.Property(prm, "A"), prm).Compile();
getB = Expression.Lambda<Func<Foo, string>>(Expression.Property(prm, "B"), prm).Compile();
getC = Expression.Lambda<Func<Foo, DateTime>>(Expression.Property(prm, "C"), prm).Compile();
Console.WriteLine("Generated: {0}", Test(foo, getA, getB, getC));
}
const int N = 1000000;
static TimeSpan Test(Foo foo, Func<Foo, int> getA, Func<Foo, string> getB, Func<Foo, DateTime> getC)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < N; i++)
{
getA(foo);
getB(foo);
getC(foo);
}
sw.Stop();
return sw.Elapsed;
}
public class Foo
{
public int A { get; set; }
public string B { get; set; }
public DateTime C { get; set; }
}
I consistently get results showing that hard-coded lambdas are about 6 times faster:
Hard-coded: 00:00:00.0115959
Generated: 00:00:00.0735896
Hard-coded: 00:00:00.0113993
Generated: 00:00:00.0648543
Hard-coded: 00:00:00.0115280
Generated: 00:00:00.0611804
Could anyone explain these results ? Is that due to compiler optimizations ? or JIT optimizations ?
Thanks for your insight
EDIT: I was running my tests with LINQPad, which compiles with optimizations enabled. When I run the same tests in VS with optimizations disabled, I get roughly the same results in both cases. So it seems the compiler was just inlining the property access in the hard-coded lambdas...
Bonus question: is there a way to optimize code generated from expression trees ?