9

I have read several posts on SO about writing and compiling dynamic C# code. For example, this post. I understand it can be done several ways.

However, calling the code invoker is slow. I did a simple benchmark, and it's some 500 X slower than calling a native method.

What I want to be able to do is the equivalent of loading a DLL and calling one of its methods directly ("natively"), which will give the speed benefits I want.

What is the easiest way to go about this? Compile the dynamic code to a dll and then load it? Can it be done in memory?

EDIT

I don't care about compilation time. Only execution.

EDIT 2, 3

Here is the benchmark code I wrote:

    public static int Execute(int i) { return i * 2; }

    private void button30_Click(object sender, EventArgs e)
    {
        CSharpCodeProvider foo = new CSharpCodeProvider();

        var res = foo.CompileAssemblyFromSource(
            new System.CodeDom.Compiler.CompilerParameters()
            {
                GenerateInMemory = true,
                CompilerOptions = @"/optimize",                    
            },
            @"public class FooClass { public static int Execute(int i) { return i * 2; }}"
        );

        var type = res.CompiledAssembly.GetType("FooClass");
        var obj = Activator.CreateInstance(type);
        var method = type.GetMethod("Execute");
        int i = 0, t1 = Environment.TickCount, t2;
        //var input = new object[] { 2 };

        //for (int j = 0; j < 10000000; j++)
        //{
        //    input[0] = j;
        //    var output = method.Invoke(obj, input);
        //    i = (int)output;
        //}

        //t2 = Environment.TickCount;

        //MessageBox.Show((t2 - t1).ToString() + Environment.NewLine + i.ToString());

        t1 = Environment.TickCount;

        for (int j = 0; j < 100000000; j++)
        {
            i = Execute(j);
        }

        t2 = Environment.TickCount;

        MessageBox.Show("Native: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());

        var func = (Func<int, int>) Delegate.CreateDelegate(typeof (Func<int, int>), method);

        t1 = Environment.TickCount;

        for (int j = 0; j < 100000000; j++)
        {
            i = func(j);
        }

        t2 = Environment.TickCount;

        MessageBox.Show("Dynamic delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());

        Func<int, int> funcL = Execute;

        t1 = Environment.TickCount;

        for (int j = 0; j < 100000000; j++)
        {
            i = funcL(j);
        }

        t2 = Environment.TickCount;

        MessageBox.Show("Delegate: " + (t2 - t1).ToString() + Environment.NewLine + i.ToString());
    }
Community
  • 1
  • 1
IamIC
  • 17,747
  • 20
  • 91
  • 154
  • Have you taken into account the compilation time? how does your benchmark look? – Botz3000 May 16 '12 at 07:22
  • 1
    executing C# build runtime is not 500x times slower than executing C# build prior. They are identical however there's overheads to be considered. What's your benchmark like, are you using Reflection.Emit or some compiler service to compile? – Rune FS May 16 '12 at 07:31
  • The Invoke is what's slow, which is the center of my question: how can you call the method at native speed? – IamIC May 16 '12 at 07:35
  • As a final note, the test above, when more accurately timed, is actually 1100 X slower. The delegate solution Marc gave is 8.4 X slower than native. – IamIC May 16 '12 at 08:43

3 Answers3

8

Yes, if you invoke via a MethodInfo or a non-specific Delegate, then it will indeed be slow. The trick is: don't do that. Various approaches:

  • for individual methods, go via a basic but typed delegate, such as Action, or as a generic catch-all, Func<object[], object> - and use Delegate.CreateDelegate to create a typed delegate:

    Action doSomething = (Action)Delegate.CreateDelegate(typeof(Action), method);
    

    another variant of this is to use the Expression API (which has a .Compile() method), or DynamicMethod (which has CreateDelegate()). The key thing: you must get a typed delegate and invoke using typed invoke (not .DynamicInvoke).

  • for more complex cases where you are generating whole types, consider implementing an interface you know about, i.e.

    IFoo foo = (IFoo)Activator.CreateInstance(...);
    

    again; after the initial cast (which is very cheap) you can just use static code:

    foo.Bar();
    

Do not use someDelegate.DynamicInvoke(...) or someMethod.Invoke(...) if you are after any kind of performance.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I fully agree with your conclusion but don't you think that a difference of magnitude of 500 is related to something else than dynamic invokation (E.g. benchmarking errors) – Rune FS May 16 '12 at 07:34
  • 1
    @RuneFS the method being tested is `return i*2;` - I think that is perfectly reasonably to expect a magitude 500 - i.e. the code being tested is vanishingly small, and all that is being tested is invoke speed. – Marc Gravell May 16 '12 at 07:38
  • @RuneFS For more information, have a look here: http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx – sloth May 16 '12 at 07:40
  • Guess I was unclear then that's my point exactly it's not the execution speed of the compiled code being measured. – Rune FS May 16 '12 at 07:41
  • Thanks much @MarcGravell :) That solves it... I'll look up those solutions in MSDN. – IamIC May 16 '12 at 07:41
  • 2
    @IanC for concrete example: `var func = (Func) Delegate.CreateDelegate(typeof (Func), method);` ... `i = func(j);` – Marc Gravell May 16 '12 at 07:43
  • @MarcGravell wouldn't it still be faster to compile to a DLL and link the DLL? This would negate the need for the delegate, which from my tests is 8.4 X slower (in this example) than a native method call. – IamIC May 16 '12 at 10:38
  • 3
    @IanC 8x sounds unlikely; in most tests the difference would be absolutely trivial. However, see my earlier point: you could also implement an interface (declared in a shared dll that both can see) and use static-binding to the interface. Typed delegate invoke is not slow - sounds like a bad test rig, to be honest. – Marc Gravell May 16 '12 at 10:44
  • @MarcGravell this is the test rig: var func = (Func) Delegate.CreateDelegate(typeof (Func), method); – IamIC May 16 '12 at 11:04
  • @MarcGravell I also used a delegate that pointed to a local static method (not dynamically compiled), and got the same speed results. – IamIC May 16 '12 at 11:05
  • @MarcGravell I updated the code to reflect my tests. I am still getting 8.4 X slower on delegates. I'm compiling to .Net 4, 64 bit. – IamIC May 16 '12 at 11:13
  • @MarcGravell I want your computer, then. We're running the same code. – IamIC May 16 '12 at 12:49
  • @IanC I assume you're in release, command-line, optimized, etc? I'm on a quad-core (8 with HT) i7. – Marc Gravell May 16 '12 at 14:54
  • I'm running the EXE (WinForm, not console) outside of VS. It's compiled for Any CPU, .Net 4.0, with optimizations on. I am running Win 7 64 bit, Core 2 Duo CPU. – IamIC May 16 '12 at 14:58
  • @MarcGravell I actually posted this as a separate question. Some posters reported a factor of 4. Not sure why there is such a wild difference between systems. – IamIC May 16 '12 at 14:59
  • @MarcGravell out of pure interest, I re-tested this on an i7 and under .Net 4.5, and it's a little over 1.4 X difference now. Same code. – IamIC Mar 01 '13 at 22:43
  • @IanC yes, interestingly reflection seems to have gotten faster, iirc – Marc Gravell Mar 01 '13 at 22:58
3

Besides Marc's advice you could improve speed by specifying the "optimize" compiler option:

var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters()
        {
            GenerateInMemory = true,
            CompilerOptions = "/optimize"
        },
Ventsyslav Raikov
  • 6,882
  • 1
  • 25
  • 28
1

Thought it was worth showing how all potential options looked and their performance characteristics. Given the following helper classes and functions:

public void Test(Func<int> func)
{        
    var watch = new Stopwatch();
    watch.Start();
    for (var i = 0; i <= 1000000; i++)
    {
        var test = func();
    }
    Console.WriteLine(watch.ElapsedMilliseconds);
}

public class FooClass { public int Execute() { return 1;}}

Set up and execution:

using (Microsoft.CSharp.CSharpCodeProvider foo = 
       new Microsoft.CSharp.CSharpCodeProvider())
{
    var res = foo.CompileAssemblyFromSource(
        new System.CodeDom.Compiler.CompilerParameters() 
        {  
            GenerateInMemory = true 
        }, 
        "public class FooClass { public int Execute() { return 1;}}"
    );

    var real = new FooClass();
    Test(() => real.Execute());                   // benchmark, direct call

    var type = res.CompiledAssembly.GetType("FooClass");
    var obj = Activator.CreateInstance(type);    
    var method = type.GetMethod("Execute");
    var input = new object[] { };                
    Test(() => (int)method.Invoke(obj, input));   // reflection invoke  

    dynamic dyn = Activator.CreateInstance(type);  
    Test(() => dyn.Execute());                    // dynamic object invoke

    var action = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), null, method); 
    Test(() => action());                         // delegate
}

The results are:

8     // direct
771   // reflection invoke
41    // dynamic object invoke
7     // delegate

So in those cases where you can't use delegates (if you don't know enough?), you can try dynamic.

yamen
  • 15,390
  • 3
  • 42
  • 52