In my previous post, I showed an example of how to use new C# features to simplify your code. In this post I’ll investigate that technique affects the performance of the code.

Let’s start from the beginning, there’s a function called f_original

static void f_original( int v1, string v2)
{
    /* do something  */
}

It takes two parameters. In this scenario I often call it with the first parameter always as 1. Like so…

f_original(1,"foo");

To avoid this repetition I want a way to consistently call with the first parameter being set to 1.

The first approach is to wrap the function.

static void f_with_wrapper(string v2)
{
    f_original(1,v2);
}

And clearly we’ll call the  wrapped function like this

f_with_wrapper( “foo” )

 

The second approach is use currying to create a new function

var f_with_currying = MAKE_FUNC(1);

where MAKE_FUNC is defined as …

 

static Action<string> MAKE_FUNC(int v1)
{
    Action<string> new_func = (string v2) => f_original(v1, v2);
    return new_func;

}

And we call this function like this

f_with_currying("foo");

 

NOTE: be aware of how often MAKE_FUNC is called. It takes time to create a function. So in the numbers I’m going to show two cases – one in which the curried function is created *every* time it is used, and another when it is statically created once and reused on each call.

 

 

Performance – the code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DemoCurrying2
{
    class Program
    {
        static void Main(string[] args)
        {

            int n = 1000 * 1000 * 10;
            Console.WriteLine("Number of repetitions: {0}",n);
            time(demo_nothing, n, "demo_nothing");
            time(demo_with_direct_call, n, "demo_with_direct_call");
            time(demo_with_wrapper_function, n, "demo_with_wrapper_function");
            time(demo_with_currying, n, "demo_with_currying");
            time(demo_with_currying2, n, "demo_with_currying2");

        }

        static void time( Action f , int n,string name)
        {
            System.DateTime start = System.DateTime.Now;
            for (int i = 0; i < n; i++)
            {
                f();
            }
            System.DateTime end = System.DateTime.Now;
            var duration = end - start;
            Console.WriteLine("{0} \t Duration \t {1}",name,duration.TotalSeconds);
        }

        static void demo_nothing()
        {
        }

        static void demo_with_direct_call()
        {
            f_original(1,"foo");
        }

        static void demo_with_wrapper_function()
        {
            f_with_wrapper("foo");
        }

        static void demo_with_currying()
        {
            var f_with_currying = MAKE_FUNC(1);
            f_with_currying("foo");
        }

        static Action<string> pre_defined_f_with_currying = MAKE_FUNC(1);

        static void demo_with_currying2()
        {
            pre_defined_f_with_currying("foo");
        }

        static void f_original( int v1, string v2)
        {
            /*var sb = new System.Text.StringBuilder();
            sb.Append("Hello World: ");
            sb.AppendFormat(" v1={0} ", v1);
            sb.AppendFormat(" v2={0} ", v2);
            string s = sb.ToString();*/
        }

        static void f_with_wrapper(string v2)
        {
            f_original(1,v2);
        }

        static Action<string> MAKE_FUNC(int v1)
        {
            Action<string> new_func = (string v2) => f_original(v1, v2);
            return new_func;

        }

    }
}

 

Performance – the numbers (part one)

 

  • The test loop 10 million times for each “demo” method
  • In this case we left f_original doing nothing so that we get a better sense of the base overhead.
  • demo_with_currying – this is the one that curries the function each time.
  • demo_with_currying – this one caches the curried function
  • demo_nothing – so that we can measure the influence of the performance testing code itself

The times

 

image

What to note:

  • We can see the impact of currying on each call to demo_with_currying
  • demo_with_currying2 and demo_with_wrapper have very similar execution times
  • demo_with_direct is only slightly faster than demo_with_currying2 and demo_with_wrapper

Performance – the numbers (part two)

 

  • In this case we left f_original I will make f_original do something small …

static void f_original( int v1, string v2)
{
    var sb = new System.Text.StringBuilder();
    sb.Append("Hello World: ");
    sb.AppendFormat(" v1={0} ", v1);
    sb.AppendFormat(" v2={0} ", v2);
    string s = sb.ToString();
}

The times

image

 

What to note:

  • Now that f_original is doing some real work – we can see that the differences between the methods are minor
  • demo_with_direct, demo_with_currying2, demo_with_wrapper – they had roughly the same execution time
  • demo_with_currying and demo_with_currying2 – the impact of currying every on every loop had a slight impact
  • demo_nothing is small

 

Parting Thoughts

 

  • Caveat Emptor – this isn’t meant to be an exhaustive evaluation of performance – just a quick sanity check to ensure. As always with any performance measurement it will depend on the specific usage scenarios and numbers. When in doubt: measure.