• 使用泛型委托,构筑最快的通用属性访问器


    最近做一个父类的属性向子类的属性赋值的小程序,用了下AutoMapper组件,感觉不错,想探究下它的原理,自己动手做一个例子试试看。实现这个功能,第一反应使用反射遍历对象的属性然后获取父类对象的属性值,接着设置给子类对象同名的属性。但一想到反射的效率,就又打算才用另外的方式来实现。

    搜索了下资料,发现了Artech写的《三种属性操作性能比较:PropertyInfo + Expression Tree + Delegate.CreateDelegate》http://www.cnblogs.com/artech/archive/2011/03/26/Propertyaccesstest.html ,文中的测试结果说明,使用委托是最快的方式,但是原文进做了原理性说明,代码不通用,于是参照原文的方法,改写成泛型方法了:

    首先,定义一个获取属性值和设置属性值的泛型委托:

     public delegate T GetPropertyValue<T>();
     public delegate void SetPropertyValue<T>(T Value);

    然后,写2个构造该委托实例的方法:

            private static ConcurrentDictionary<string, Delegate> myDelegateCache = new ConcurrentDictionary<string, Delegate>();
    
            public static GetPropertyValue<T> CreateGetPropertyValueDelegate<TSource, T>(TSource obj, string propertyName)
            {
                string key = string.Format("Delegate-GetProperty-{0}-{1}", typeof(TSource).FullName, propertyName);
                GetPropertyValue<T> result = (GetPropertyValue<T>)myDelegateCache.GetOrAdd(
                    key, 
                    newkey =>
                    {
                        return Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod());
                    }
                    );
    
                return result;
            }
            public static SetPropertyValue<T> CreateSetPropertyValueDelegate<TSource, T>(TSource obj, string propertyName)
            {
                string key = string.Format("Delegate-SetProperty-{0}-{1}", typeof(TSource).FullName, propertyName);
                SetPropertyValue<T> result = (SetPropertyValue<T>)myDelegateCache.GetOrAdd(
                   key,
                   newkey =>
                   {
                       return Delegate.CreateDelegate(typeof(SetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetSetMethod());
                   }
                   );
    
                return result;
            }

    注意,上面使用了.net 4.0的线程安全的字典来缓存生成的委托类型实例:
    private static ConcurrentDictionary<string, Delegate> myDelegateCache = new ConcurrentDictionary<string, Delegate>();

    好了,下面我们写一点测试代码:

                CarInfo info = new CarInfo();
                info.CID = 999;
                ////////////////
                //info.ID==999;
                SetPropertyValue<int> set = CreateSetPropertyValueDelegate<CarInfo, int>(info, "CID");
                set(100);//100;
    
                GetPropertyValue<int> get = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID");
                var r = get();//100
                GetPropertyValue<int> get2 = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID");
                var r2 = get2();//100

    经测试,结果正常,这样,通用的最快的属性访问器就有了。

    这个方法的性能怎么样?听说.net的字典查找性能不够高,继续写段测试代码跑跑看:

    (注:测试代码增加了相应的NoCache方法,代码如下:

     public static GetPropertyValue<T> CreateGetPropertyValueDelegateNoCache<TSource, T>(TSource obj, string propertyName)
            {
                return (GetPropertyValue<T>)Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod()); ;
            }
            public static SetPropertyValue<T> CreateSetPropertyValueDelegateNoCache<TSource, T>(TSource obj, string propertyName)
            {
                return (SetPropertyValue<T>)Delegate.CreateDelegate(typeof(SetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetSetMethod()); ;
            }

    Console.WriteLine("{0, -15}{1,-10}{2,-8}{3,-8} {4,-8}", "Times", "直接访问", "委托(非缓存)", "委托(字典缓存)", "委托(变量缓存)");
                //性能测试 直接访问
                int times = 1000000;
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < times; i++)
                {
                    int oldValue = info.CID;
                    info.CID = i;
                }
                var duration1 = stopwatch.ElapsedMilliseconds;
                //使用委托,非缓存
                stopwatch.Restart();
                for (int i = 0; i < times; i++)
                {
                    int oldValue = CreateGetPropertyValueDelegateNoCache<CarInfo, int>(info, "CID")();
                    CreateSetPropertyValueDelegateNoCache<CarInfo, int>(info, "CID")(i);
                }
                var duration2 = stopwatch.ElapsedMilliseconds;
                //使用委托,字典缓存
                stopwatch.Restart();
                for (int i = 0; i < times; i++)
                {
                    int oldValue = CreateGetPropertyValueDelegate<CarInfo, int>(info, "CID")();
                    CreateSetPropertyValueDelegate<CarInfo, int>(info, "CID")(i);
                }
                var duration3 = stopwatch.ElapsedMilliseconds;
    
                //使用委托,直接缓存
                stopwatch.Restart();
                for (int i = 0; i < times; i++)
                {
                    int oldValue = get();
                    set(i);
                }
                var duration4 = stopwatch.ElapsedMilliseconds;
                stopwatch.Stop();
                Console.WriteLine("{0, -15} {1,-15} {2,-15}  {3,-15}  {4,-15} ", times, duration1, duration2, duration3, duration4);
                Console.WriteLine("--------------------单位(ms)--------------------------");

    下面是运行结果:

    ===================Debug模式=====================
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         17              12421            949              16
    
    --------------------单位(ms)----------------------------------------------
    
    ====================Release模式=========================
    
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         9               11696            867              8
    
    --------------------单位(ms)--------------------------

    结果令人惊异:直接执行委托调用,比调用方法本身还要快点,另外也证明了,字典的查找性能的确不高。这个测试中字典元素的数量是较少的,有朋友提示,可能是计算字典的Key的哈希耗费了较多性能,于是将缓存字典的长度改小成DGP-{0}-{1} 和 DSP-{0}-{1},再次进行测试:

    ========================long key==================================
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         19              11817            971              18
    
    --------------------单位(ms)--------------------------
    
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         24              11210            882              16
    
    --------------------单位(ms)--------------------------
    
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         22              11168            895              16
    
    --------------------单位(ms)--------------------------
    ========================short key==================================
    
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         20              11727            689              18
    
    --------------------单位(ms)--------------------------
    
    imes          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         18              11804            738              17
    
    -------------------单位(ms)--------------------------
    
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         23              11234            684              16
    
    --------------------单位(ms)--------------------------

    测试结果表明,字典的Key的长度的确对性能有影响,所以我们需要注意Key不是越长越好。

    补充:

    下面有朋友回复说,要比较它在10次,100,10000,1000000 不同情况下面的效率,将测试程序稍微改写了下,下面是运行结果:

    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    10              0               3                0                0
    
    --------------------单位(ms)--------------------------
    *
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    100             0               1                0                0
    
    --------------------单位(ms)--------------------------
    *
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    10000           0               165              8                0
    
    --------------------单位(ms)--------------------------
    *
    Times          直接访问      委托(非缓存) 委托(字典缓存) 委托(变量缓存)
    1000000         31              11556            755              17
    
    --------------------单位(ms)--------------------------
    *

    从测试来看,在执行次数在几百次的范围内,效率相差都是很小的,可以忽略,所以不用缓存委托结果也行。

    它能做什麽?

    在动态构设置对象的属性值的地方,比如ORM的实体类属性赋值,用途很大的。

     PS:今天测试发现,代码

    Delegate.CreateDelegate(typeof(GetPropertyValue<T>), obj, typeof(TSource).GetProperty(propertyName).GetGetMethod());
    创建的委托方法仅针对当前实例对象 obj 有效,除非这是静态属性,它并不能作为一个通用类型的属性访问器,所以将它缓存意义不大,但可以作为优化属性访问的一个手段。

     ------------------分界线---------------------------

    欢迎加入PDF.NET开源技术团队,做最轻最快的数据框架!

  • 相关阅读:
    LeetCode刷题记录
    开始学习Functional Programming
    明天软软onsite
    Criteo电面二
    Jet.com
    还需要补充很多知识
    重新粗推了一下Master Theorem
    买了第一台mac
    病来如山倒
    Integer.parseInt vs Integer.valueOf
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/2823325.html
Copyright © 2020-2023  润新知