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の名前も出てきていますね。