Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is using delegates excessively a bad idea for performance? [duplicate]

Consider the following code:

if (IsDebuggingEnabled) { 
   instance.Log(GetDetailedDebugInfo()); 
}

GetDetailedDebugInfo() may be an expensive method, so we only want to call it if we're running in debug mode.

Now, the cleaner alternative is to code something like this:

instance.Log(() => GetDetailedDebugInfo());

Where .Log() is defined such as:

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

My concern is with performance, preliminary testing doesn't show the second case to be particularly more expensive, but I don't want to run into any surprises if load increases.

Oh, and please don't suggest conditional compilation because it doesn't apply to this case.

(P.S.: I wrote the code directly in the StackOverflow Ask a Question textarea so don't blame me if there are subtle bugs and it doesn't compile, you get the point :)

like image 493
andreialecu Avatar asked Aug 13 '09 00:08

andreialecu


3 Answers

No, it shouldn't have a bad performance. After all, you'll be calling it only in debug mode where performance is not at the forefront. Actually, you could remove the lambda and just pass the method name to remove the overhead of an unnecessary intermediate anonymous method.

Note that if you want to do this in Debug builds, you can add a [Conditional("DEBUG")] attribute to the log method.

like image 142
mmx Avatar answered Sep 24 '22 14:09

mmx


There is a difference in performance. How significant it is will depend on the rest of your code so I would recommend profiling before embarking on optimisations.

Having said that for your first example:

if (IsDebuggingEnabled) 
{ 
    instance.Log(GetDetailedDebugInfo()); 
}

If IsDebuggingEnabled is static readonly then the check will be jitted away as it knows it can never change. This means that the above sample will have zero performance impact if IsDebuggingEnabled is false, because after the JIT is done the code will be gone.

instance.Log(() => GetDetailedDebugInfo());

public void Log(Func<string> getMessage)
{
    if (IsDebuggingEnabled) 
    {
        LogInternal(getMessage.Invoke());
    }
}

The method will be called every time instance.Log is called. Which will be slower.

But before expending time with this micro optimization you should profile your application or run some performance tests to make sure this is actually a bottle neck in your application.

like image 42
trampster Avatar answered Sep 22 '22 14:09

trampster


I was hoping for some documentation regarding performance in such cases, but it seems that all I got were suggestions on how to improve my code... No one seems to have read my P.S. - no points for you.

So I wrote a simple test case:

    public static bool IsDebuggingEnabled { get; set; }


    static void Main(string[] args)
    {
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                Log(GetDebugMessage);
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }

        Console.ReadLine();
        for (int j = 0; j <= 10; j++)
        {
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i <= 15000; i++)
            {
                if (IsDebuggingEnabled) GetDebugMessage();
                if (i % 1000 == 0) IsDebuggingEnabled = !IsDebuggingEnabled;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
        Console.ReadLine();
    }

    public static string GetDebugMessage()
    {
        StringBuilder sb = new StringBuilder(100);
        Random rnd = new Random();
        for (int i = 0; i < 100; i++)
        {
            sb.Append(rnd.Next(100, 150));
        }
        return sb.ToString();
    }

    public static void Log(Func<string> getMessage)
    {
        if (IsDebuggingEnabled)
        {
            getMessage();
        }
    }

Timings seem to be exactly the same between the two versions. I get 145 ms in the first case, and 145 ms in the second case

Looks like I answered my own question.

like image 24
andreialecu Avatar answered Sep 22 '22 14:09

andreialecu