4

I've run into some speed issues with regards to structs and delegates - take the following console application code:

public delegate string StringGetter();
public class LocalString
{
    public LocalString(string value)
    {
        this.value = value;
    }

    public StringGetter Getter
    {
        get
        {
            return new StringGetter(this.GetValue);
        }
    }

    private string GetValue()
    {
        return value;
    }

    private string value;
}


class Program
{
    static void Main(string[] args)
    {
        var start = DateTime.Now;
        for (int i = 0; i < 2000000; i++)
        {
            var val = new LocalString( "hello World" );
            val.Getter();
        }
        Console.WriteLine((DateTime.Now - start).TotalMilliseconds);
        Console.ReadKey();
    }
}

When executed on my machine it takes ~1.8 secs...If I change the struct to a class it runs in ~0.1secs. I've had a look at the underlying assembly code and open source ROTOR code to see why and there is some special code for delegates that have a struct target which I'm guessing is for handling boxing and unboxing in function MethodDesc* COMDelegate::GetDelegateCtor(TypeHandle delegateType, MethodDesc *pTargetMethod, DelegateCtorArgs *pCtorData).

Another point - if you build this in VS2008 targeting .net 3.5 the app runs faster than if you run it in VS2010 targeting .net 3.5. I haven't figured out why this is.

Any comments / better enlightenment would be welcome...

Regards Lee

  • 1
    The example is partially contrived (some yanked from real code) just to repro the issue. Admittedly I could have reduced it down further but it does compile and run. – Lee Alexander Nov 10 '11 at 09:29
  • 1
    @leppie: I suggest you actually try to compile it, and then reevaluate your position... – Willem van Rumpt Nov 10 '11 at 09:37
  • @WillemvanRumpt: I concede. :) The question though is why would one do `new StringGetter(somestringgetter)` instead of `instance.Getter` ? In fact I find it strange that a delegate has such a constructor (taking a delegate of the same type). I see no purpose in such 'magic'. – leppie Nov 10 '11 at 09:42
  • To clarify my point: `var k = new Action(new Action( new Action (K)));` is valid, and completely bloody horrible code. – leppie Nov 10 '11 at 09:44
  • @leppie: Oh, we can agree on the high whiteboardscreechiness value of the code :) – Willem van Rumpt Nov 10 '11 at 09:48
  • yes I agree but THAT DOESN'T answer the questions does it! Just to make you happy I've updated the example. – Lee Alexander Nov 10 '11 at 09:55
  • Posted a question regarding this funny delegate behavior: http://stackoverflow.com/q/8077719/15541 (PS: Lee, I did +1 you after I realized this horrible behavior). – leppie Nov 10 '11 at 10:00

1 Answers1

3

This is hard to answer accurately, the CLR support code for delegates is a tough nut to crack. My best guess is the overhead required to un/box the struct value. The delegate call is made through a stub that first boxes the value so the instance method can be called. After the call, any side-effects of the method needs to be copied back to the original struct. That's expensive compared to a simple call to the instance method of a reference type, they are very fast. I didn't see any evidence for validating the liveness of the struct value, a bit odd, but might well be there somewhere.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Sounds like a good explanation to me! It still leaves the question as to why an exe build with 2010 .net v3.5 is slightly slower than one generated by VS2008 .net v3.5... Anyway good answer, cheers! – Lee Alexander Nov 10 '11 at 14:10
  • If you're sure they both run on the same .Net, then the only difference can be in the IL. You can easily check that with some tool like Reflector or ildasm. – svick Nov 10 '11 at 16:07