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から同じ名前、同じ型のプロパティを見つけて、それを同期するというものです。
詳細は↓こんな感じ。

  1. ReflectionでPropertyInfoをごっそり取得
  2. 名前と型が一致するプロパティを取ってくる
  3. PropertyBinderを実行時型生成
  4. MethodCacheにPropertyInfoを渡し、getter/setterからDelegateを生成
  5. プロパティを同期したくなったらSynchronize


という流れになります。
これならMethodInfo.Invokeを呼ぶより高速ですし、値型のプロパティを扱ってもboxing/unboxingがおきないはずです。