Reflectionって遅いよね?
C++にはなかったReflection。
C#になってSerializeなんかで良く活躍するわけですが、
大量のインスタンスをReflectionを使って処理したりすると思いのほか時間がかかります。
そこでReflectionをなんとかして高速化しようというのが本日のエントリ。
NyaRuRuさんのとこの
効率の良い実行時バインディングとインターフェイス指向プログラミングでの boxing の回避テクニック - NyaRuRuが地球にいたころ
パフォーマンスのための Delegate, LCG, LINQ, DLR (を後で書く) - NyaRuRuが地球にいたころ
を参考にしています。
それでは実際のコードを。
using System; using System.Collections.Generic; namespace ReflectionAccelarator { public class MethodCache<TDelegate> : IDisposable where TDelegate : class { private TDelegate cache = null; public MethodCache(System.Reflection.MethodInfo method) { if (null == method) { throw new ArgumentNullException("method"); } this.cache = Delegate.CreateDelegate(typeof(TDelegate), method) as TDelegate; if (null == this.cache) { throw new MissingMethodException(); } if (!(this.cache is Delegate)) { throw new MethodAccessException(); } } public MethodCache(TDelegate method) { if (null == method) { throw new ArgumentNullException("method"); } Delegate d = method as Delegate; if (null == d) { throw new ArgumentException("TDelegate type parameter \"method\" is not delegate.", "method"); } this.cache = method; } public TDelegate Method { get { return this.cache; } } #region IDisposable Members public void Dispose() { this.cache = null; } #endregion } public interface IBinder : IDisposable { void Synchronize(object src, object dst); } public interface IBinder<TSrc, TDst> : IBinder { void Synchronize(TSrc src, TDst dst); } public class PropertyInfoPair { private System.Reflection.PropertyInfo srcPi; private System.Reflection.PropertyInfo dstPi; public PropertyInfoPair(System.Reflection.PropertyInfo src, System.Reflection.PropertyInfo dst) { this.srcPi = src; this.dstPi = dst; } public System.Reflection.PropertyInfo Src { get { return this.srcPi; } } public System.Reflection.PropertyInfo Dst { get { return this.dstPi; } } } public class PropertyBinder<TSrc, TDst, TProp> : IBinder<TSrc, TDst> { private delegate TValue generic_getter<TInst, TValue>(TInst obj); private delegate void generic_setter<TInst, TValue>(TInst obj, TValue value); private MethodCache<generic_getter<TSrc, TProp>> src_getter_cache; private MethodCache<generic_setter<TDst, TProp>> dst_setter_cache; public PropertyBinder(PropertyInfoPair p) { System.Reflection.MethodInfo getter_mi = p.Src.GetGetMethod(); System.Reflection.MethodInfo setter_mi = p.Dst.GetSetMethod(); if (null == getter_mi) { throw new MissingMethodException(); } if (null == setter_mi) { throw new MissingMethodException(); } this.src_getter_cache = new MethodCache<generic_getter<TSrc, TProp>>(getter_mi); this.dst_setter_cache = new MethodCache<generic_setter<TDst, TProp>>(setter_mi); if (null == this.src_getter_cache.Method) { throw new MissingMethodException(); } if (null == this.dst_setter_cache.Method) { throw new MissingMethodException(); } } public PropertyBinder(System.Reflection.PropertyInfo src, System.Reflection.PropertyInfo dst) { System.Reflection.MethodInfo getter_mi = src.GetGetMethod(); System.Reflection.MethodInfo setter_mi = dst.GetSetMethod(); if (null == getter_mi) { throw new MissingMethodException(); } if (null == setter_mi) { throw new MissingMethodException(); } this.src_getter_cache = new MethodCache<generic_getter<TSrc, TProp>>(getter_mi); this.dst_setter_cache = new MethodCache<generic_setter<TDst, TProp>>(setter_mi); if (null == this.src_getter_cache.Method) { throw new MissingMethodException(); } if (null == this.dst_setter_cache.Method) { throw new MissingMethodException(); } } #region IBinder<TSrc,TDst> Members public void Synchronize(object src, object dst) { this.Synchronize((TSrc)src, (TDst)dst); } public void Synchronize(TSrc src, TDst dst) { this.dst_setter_cache.Method(dst, this.src_getter_cache.Method(src)); } #endregion #region IDisposable Members public void Dispose() { this.src_getter_cache.Dispose(); this.dst_setter_cache.Dispose(); } #endregion } public class ClassPropertyBinder : IDisposable { protected List<IBinder> binderList; public ClassPropertyBinder(Type src, Type dst) { binderList = new List<IBinder>(); List<PropertyInfoPair> pi_list = new List<PropertyInfoPair>(); System.Reflection.PropertyInfo[] src_pis = src.GetProperties(); System.Reflection.PropertyInfo[] dst_pis = dst.GetProperties(); foreach (System.Reflection.PropertyInfo src_pi in src_pis) { System.Reflection.PropertyInfo dst_pi = dst.GetProperty(src_pi.Name, src_pi.PropertyType); if (null != dst_pi) { pi_list.Add(new PropertyInfoPair(src_pi, dst_pi)); } } foreach (PropertyInfoPair pi_pair in pi_list) { Type property_binder_type = typeof(PropertyBinder<,,>).MakeGenericType(pi_pair.Src.ReflectedType, pi_pair.Dst.ReflectedType, pi_pair.Src.PropertyType); IBinder binder = null; try { binder = Activator.CreateInstance(property_binder_type, pi_pair) as IBinder; } catch (System.Reflection.TargetInvocationException ex) { System.Diagnostics.Debug.WriteLine(ex); } if (null != binder) { this.binderList.Add(binder); } } } public void SynchronizeAllProperties(object src, object dst) { foreach (IBinder binder in this.binderList) { binder.Synchronize(src, dst); } } #region IDisposable Members public void Dispose() { foreach (IBinder binder in binderList) { binder.Dispose(); } } #endregion } public class ClassPropertyBinder<TSrc, TDst> : ClassPropertyBinder where TSrc : class where TDst : class { public ClassPropertyBinder() : base(typeof(TSrc), typeof(TDst)) { } public void SynchronizeAllProperties(TSrc src, TDst dst) { foreach (IBinder<TSrc, TDst> binder in base.binderList) { binder.Synchronize(src, dst); } } } }
これを使って
using System; using System.Collections.Generic; namespace Test { using ReflectionAccelarator; class Program { [STAThread] public static void Main(string[] args) { System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch(); Foo foo = new Foo(); Bar bar = new Bar(); using (ClassPropertyBinder<Foo, Bar> binder = new ClassPropertyBinder<Foo, Bar>()) { s.Start(); for (int i = 1; i < 1000000; ++i) { foo.Value = i; typeof(Bar).GetProperty("Value").GetSetMethod().Invoke(bar, new object[] { typeof(Foo).GetProperty("Value").GetGetMethod().Invoke(foo, null) }); } s.Stop(); Console.WriteLine(s.ElapsedMilliseconds); s.Reset(); s.Start(); for (int i = 1; i < 1000000; ++i) { foo.Value = i; binder.SynchronizeAllProperties(foo, bar); } s.Stop(); Console.WriteLine(s.ElapsedMilliseconds); } } } public class Foo { private int value; public int Value { get { return this.value; } set { this.value = value; } } } public class Bar { private int value; public int Value { get { return this.value; } set { this.value = value; } } } }
こんなことをしてみるとパフォーマンスがだいぶ違うなぁというのがわかるかと。
それにしてもGenericのパラメータ制約にDelegateを指定できないのがなんとも。
で、上のコードで何をしているかですが、
FooとBarから同じ名前、同じ型のプロパティを見つけて、それを同期するというものです。
詳細は↓こんな感じ。
- ReflectionでPropertyInfoをごっそり取得
- 名前と型が一致するプロパティを取ってくる
- PropertyBinderを実行時型生成
- MethodCacheにPropertyInfoを渡し、getter/setterからDelegateを生成
- プロパティを同期したくなったらSynchronize
という流れになります。
これならMethodInfo.Invokeを呼ぶより高速ですし、値型のプロパティを扱ってもboxing/unboxingがおきないはずです。