DynamicObjectのパフォーマンス

通常のメソッド呼び出しよりもDynamicObjectのメソッド呼び出しの方が遅いのは当然ですが、どの程度違うのか実験君。

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

namespace DynamicSample
{
    // 普通のクラス
    class Poco
    {
        public int Add(int x, int y)
        {
            return x + y;
        }
    }

    // 動的なクラス
    class Dynamic : DynamicObject
    {
        Dictionary<string, object> dict = new Dictionary<string, object>();

        public override void SetMember(System.Scripting.Actions.SetMemberAction info, object value)
        {
            dict[info.Name] = value;
        }

        public override object GetMember(System.Scripting.Actions.GetMemberAction info)
        {
            return dict[info.Name];
        }

        public override object InvokeMember(System.Scripting.Actions.CallAction info, object arguments)
        {
            dynamic target = dict[info.Name];
            if (arguments.Length == 2)
                return target(this, arguments[0], arguments[1]);
            return base.InvokeMember(info, arguments);
        }
    }

    class Program
    {
        const int LOOP = 10000000;
        static void Main(string args)
        {
            // 実行時にオブジェクトにメソッドを追加する
            dynamic d = new Dynamic();
            d.Add = new Func<object, int, int, int>((self, x, y) => x + y);
            d.Add(1, 2);

            // 比較用の普通のオブジェクト
            Poco p = new Poco();

            // 普通のクラスのメソッド呼び出し
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < LOOP; ++i)
                p.Add(i, i);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            // 動的オブジェクトのメソッド呼び出し
            sw.Reset();
            sw.Start();
            for (int i = 0; i < LOOP; ++i)
                // Call Site Cachingが使われる
                d.Add(i, i);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            // dynamicとしてメソッドに渡す
            Bench(p);
            Bench(d);
        }

        static void Bench(dynamic obj)
        {
            // Call Site Cachingが使われる
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < LOOP; ++i)
                obj.Add(i, i);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }
    }
}
/*
82
1990
280
1939
 */

1〜2桁程度なので思ったよりは速いかな? もちろん、キャッシングが効いているお陰でしょうけど。reflectorでMainメソッドを逆コンパイルしてみると、

private static void Main(string[] args)
{
    int i;
    object d = new Dynamic();
    if (<Main>o__SiteContainer0.<>p__Site1 == null)
    {
        <Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, object, object>>.Create(
            new CSharpSetMemberPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), true, null, "Add"));
    }
    <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d, delegate (object self, int x, int y) {
        return x + y;
    });
    if (<Main>o__SiteContainer0.<>p__Site2 == null)
    {
        <Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object, int, int>>.Create(
            new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), 
                false, false, "Add", typeof(object), null));
    }
    <Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, d, 1, 2);
    Poco p = new Poco();
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (i = 0; i < 0x989680; i++)
    {
        p.Add(i, i);
    }
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
    sw.Reset();
    sw.Start();
    for (i = 0; i < 0x989680; i++)
    {
        if (<Main>o__SiteContainer0.<>p__Site3 == null)
        {
            <Main>o__SiteContainer0.<>p__Site3 = CallSite<Action<CallSite, object, int, int>>.Create(
                new CSharpCallPayload(Microsoft.CSharp.RuntimeBinder.RuntimeBinder.GetInstance(), 
                    false, false, "Add", typeof(object), null));
        }
        <Main>o__SiteContainer0.<>p__Site3.Target(<Main>o__SiteContainer0.<>p__Site3, d, i, i);
    }
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
    Bench(p);
    Bench(d);
}

あちこちにCallSiteが埋め込まれています。

あと、Microsoft.CSharp.RuntimeBinder.RuntimeBinderとBinderの名前も出てきていますね。