Anonymous Delegates vs Lambda Expressions vs Function Calls Performance

December 11, 2007

5 comments

Recently I’ve worked on some project, where I needed to investigate performance issue. After some investigation, some profiling I’ve got suspicion, that some function being called a lot of times (more than 100,000 calls in some very specific and relatively short execution path) in profiled code part. It was to strange to see, how relatively simple function consuming so much CPU time. The function did some relative simple arithmetic calculations, based on 2-3 received as parameters. While trying to solve this, I’ve created some test project to simulate the situation.

Lets assume, we have the following function:

private double CalcPurchaseTaxes(double Amount, double localTax, double? additionalTax)
{
    if (null == additionalTax)
        additionalTax = 0;

    return Amount + Amount * localTax + Amount * (double)additionalTax;
}

When executing this in loop of 10,000,000 calls to simulate real function(which is obviously more complex) execution i’ve got average total of 00:00:00.3689428ms execution time.

Well, because my function is very simple (and also real project function), I decide to rewrite it as anonymous delegate function. That’s what I’ve got:

//Delegate declaration
delegate double TaxCalculator(double Amount, double LocalTax, double AdditionalLocalTax);
///....

//Delegate implementation
TaxCalculator grossPrice = delegate(double Amount, double LocalTax, double AdditionalLocalTax)
{
    return Amount + Amount * LocalTax + Amount * AdditionalLocalTax;
};
///.....

Execution of this delegate for the same 10,000,000 times get me much better average result of 00:00:00.2163977ms. Pretty good improvement in terms of percentage…

I almost stopped there, but decided to try lambda expressions which actually should been translated to IL as anonymous delegates also…

Here is the very same calculation written with new C# 3.0 lambda expressions syntax:

////////////////////////////////////////////////////////////////////////////////////////////
Func<double, double, double> absoluteTax = (amt, tax) => amt * tax;
Func<double, double> grossPrice = amt => amt + absoluteTax(amt, localTax) + absoluteTax(amt, additionalLocalTax);

Well, definitely shortest syntax and for me also pretty nice… But what about performance? Hm… same 10,000,000 iterations, got average of 00:00:00.3349174ms…

Why?!? Shouldn’t it be about delegate timing or even faster??? Shouldn’t compiler generate more efficient code, that it did for delegates?

From looking on generated IL code, I’ve got even more strange picture: class generated by compiler from lambdas as expected is smaller, simpler and derives directly from System.Object.

Class created by compiler for delegates as expected derives from System.MulticastDelegate and has a lot of unused (at least in this simple case) stuff.

So, the confusion is still there – why lambdas performance less good comparing to anonymous delegates…

As for me, I expected to get delegates/lambdas with almost same results and function call behind them…

Ideas? Comments?

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

5 comments

  1. עומר ון קלוטןDecember 11, 2007 ב 22:02

    Your test cases differ, so your results differ:
    Your first case has a larger 3rd variable (Nullable(Of Double)) and checks for nulls (i.e. calls to the HasValue property). Your third test is three function calls (grossPrice which calls absoluteTax twice).

    Under the hood, lambdas are anonymous methods, which in turn, under the hood, are methods constructed at compile time. The only performance hit you may experience is the call via delegate rather than a direct il callvirt, which is understandable.

    Reply
  2. Alex GoleshDecember 12, 2007 ב 7:36

    You right Omer! My bad… After changing third parameter in delegated code and performing null check in there I’ve got comparable results with lambdas…
    Thanks for pointing out.
    Alex

    Reply
  3. mainguyMarch 23, 2009 ב 14:18

    Also remember, if your runtime has JIT (I think .net does) and it uses a heuristic to compile things, you might end up not testing what you think you’re testing. Microbenchmarks are very hard to do with JIT runtimes, I’ve been burned a dozen times with this in java.

    Reply
  4. Robert KoritnikNovember 24, 2011 ב 10:52

    Regarding your lambda expressions. Have you tried using a bit different lambda (eliminating the need for non existing additional tax just as you did):


    Func grossPrice = (amt, locTax, addTax) => amt + amt * locTax + amt * addTax;

    This one is the actual equivalent to your anonymous delegate. Then try to compare it with this one:


    Func multiply = (amt, tax) => amt * tax;
    Func
    grossPrice = (amt, locTax, addTax) => amt + multiply(amt, locTax) + multiply(amt, addTax);

    It may turn out that both variants may be faster than your lambda solution. Why? Because if local tax and additional local tax are variables, your lambda needs to be recreated on each call and not reused, since it uses external variables that aren’t provided as parameters to lambda expression.

    Hopefully this may be of some help.

    Reply
  5. Erika March 28, 2012 ב 7:42

    So-so. Something was not impressed.

    Reply