Why delegates generated dynamically from expressions are slower than hard-coded lambdas?
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 ?
Just a guess, but i think it's the optimizer that sees that the Lambda expressions are simple getters and turns them into direct access or similar. The generated Expressions cannot be optimized so they result in slower code.
It should happen at compile time so you should try it with optimizations disabled and check the results again.
There's something else going on. The benchmark is critically dependent on the .NET Platform Target you select. I do repro the results for .NET 4.0 but .NET 3.5 is consistently faster on the generated version, regardless of the build type.
Guessing what might have changed in 4.0 to make it so much slower is too difficult, this is not easy code to analyze. And expression tree support has significantly changed. I recommend you post feedback to connect.microsoft.com, do expect a "by design" response. Hopefully annotated.
精彩评论