I expected that delegates created from expression trees would achieve roughly the same performance as hard-coded, static equivalent anonymous methods. However, it seems that dynamically created delegates are noticeably slower ...
Here is a simple test program illustrating the case. It simply accesses 3 properties of an object 1,000,000 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 => fA; getB = f => fB; getC = f => fC; 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
Can anyone explain these results? Is this related to compiler optimization? or JIT optimization?
Thank you for understanding.
EDIT: I ran my tests with LINQPad, which compiles with optimizations enabled. When I run the same tests in VS with optimizations disabled, I get about the same results in both cases. Thus, it seems that the compiler simply injected access to properties into hard-coded lambdas ...
Bonus question: is there a way to optimize the code generated from expression trees?