image I have spent the last couple of days trying to find ways to parallelize GenArt WPF using Parallel.For (from the Parallel Extensions Library). In the process I stumbled upon a scenario where using Lambdas/anonymous delegates can have pretty substantial performance implications.

The code in question looked something like this:
internal void Mutate(EvolutionContext context)
{
    context.IfMutation(MutationType.AddPoint, () =>
    {
        AddPoint(context);
    });
        
    ///...
}

The function above was called in a while loop until a mutation hit was generated. I was not seeing the CPU utilization I was expecting. I was expecting 100% but got around 80% which I found strange, there was nothing that I could see that would cause a thread lock. To find the cause I started commenting out code. It was when I commented out the code above that I immediately saw the CPU utilization jump 100%. It must have been the garbage collector that caused the decrease in CPU utilization. Why the garbage collector? Well the code above will actually compile to something very different.

Something like this (reconstructed approximation of the generated IL):

public class c__DisplayClass1
{
    public GeneticPolygon __this;
    public EvolutionContext contex;

    public void Mutate__0()
    {
        __this.AddPoint(contex);
    }
}

internal void Mutate(EvolutionContext context)
{
    var lambdaClass = new c__DisplayClass1();
    lambdaClass.__this = this;
    lambdaClass.contex = context;

    context.IfMutation(MutationType.AddPoint, lambdaClass.Mutate__0);
    
    ///...
}

As you see the C# compiler actually creates a separate class to hold the lambda method body, a class that it will be instantiated every time the Mutate method is called. The reason for this is that it needs to capture the local variables (this is was makes lambdas/anonymous delegates true closures). I was well aware that this was happening but I have never encounter a situation where this has had any noticeable performance implications, until now that is.

The fact that lambda methods that use local variables will result in an instantiation of a new object should not be a problem 99% of the time, but as this shows it is worth being aware of because in some cases it can matter a great deal.

0 comments: