ExpressionTree——让反射性能向硬编码看齐

缘起

最近又换了工做。而后开心是之后又能比较频繁的关注博客园了。办离职手续的这一个月梳理了下近一年本身写的东西,而后就有了此文以及附带的代码。html

反射

关于反射,窃觉得,他只是比较慢。在这个前提下,我的认为只有在进行集合操做的时候谈论反射的性能才有意义。同时,只有在这个集合达到必定规模的时候,才有改进的余地。好比说,1000个元素的集合。后文会给出详细的数据。express

关于ExpressionTree的介绍

借花献佛:编程

http://www.cnblogs.com/ASPNET2008/archive/2009/05/22/1485888.html缓存

http://www.cnblogs.com/jams742003/archive/2009/12/23/1630432.html编程语言

实现

先给例子,再来探讨为何。这里是几个Case:性能

1.根据属性名提取一个IEnuerable<object>集合元素的属性/字段的值。测试

2.根据属性名更新上诉对象的元素的属性/字段值。this

3.将上诉集合投影为指定元素类型的集合。编码

如下是针对这3个Case的实现:spa

1.加载属性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Caching;
using System.Text;
using System.Extension;
using System.Collections.Concurrent;

namespace System.Linq.Expressions
{
    public class PropertyFieldLoader : CacheBlock<string, Func<object, object>>
    {
        protected PropertyFieldLoader() { }

        public object Load(object tObj, Type type, string propertyPath)
        {
            return Compile(type, propertyPath)(tObj);
        }

        public T Load<T>(object tObj, Type type, string propertyPath)
            where T : class
        {
            return Load(tObj, type, propertyPath) as T;
        }

        public Func<object, object> Compile(Type type, string propertyPath)
        {
            var key = "Get_" + type.FullName + "_";
            var func = this.ConcurrentDic.GetOrAdd(key + "." + propertyPath, (string k) =>
            {
                ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj");
                Expression expression = Expression.Convert(paramInstance, type);
                foreach (var pro in propertyPath.Split('.'))
                    expression = Expression.PropertyOrField(expression, pro);
                expression = Expression.Convert(expression, typeof(object));
                var exp = Expression.Lambda<Func<object, object>>(expression, paramInstance);
                return exp.Compile();
            });
            return func;
        }

        public static PropertyFieldLoader Instance = new PropertyFieldLoader();
    }
}

2.更新属性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Caching;
using System.Text;
using System.Extension;
using System.Collections.Concurrent;

namespace System.Linq.Expressions
{
    public class PropertyFieldSetter : CacheBlock<string, Action<object, object>>
    {
        protected PropertyFieldSetter() { }

        public void Set(object obj, string propertyPath, object value)
        {
            var tObj = obj.GetType();
            var tValue = value.GetType();
            var act = Compile(tObj, tValue, propertyPath);
            act(obj, value);
        }

        public static PropertyFieldSetter Instance = new PropertyFieldSetter();

        public Action<object, object> Compile(Type typeObj,Type typeValue, string propertyPath)
        {
            var key = "Set_" + typeObj.FullName + "_" + typeValue.FullName;
            var act = ConcurrentDic.GetOrAdd(key + "." + propertyPath, (s) =>
            {
                ParameterExpression paramInstance = Expression.Parameter(typeof(object), "obj");
                ParameterExpression paramValue = Expression.Parameter(typeof(object), "value");
                Expression expression = Expression.Convert(paramInstance, typeObj);
                foreach (var pro in propertyPath.Split('.'))
                    expression = Expression.PropertyOrField(expression, pro);
                var value = Expression.Convert(paramValue, typeValue);
                expression = Expression.Assign(expression, value);
                var exp = Expression.Lambda<Action<object, object>>(expression, paramInstance, paramValue);
                return exp.Compile();
            });
            return act;
        }
    }
}

3.数据转换

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Caching;

namespace System.Linq.Expressions
{
    public class DataTransfer<T> : CacheBlock<string, Func<object, T>>
         where T : new()
    {
        protected DataTransfer() { }

        public Func<object, T> Compile(Type inType)
        {
            var outType = typeof(T);
            var pKey = "Transfer_" + inType.FullName + "_" + outType.FullName;

            var func = this.ConcurrentDic.GetOrAdd(pKey, (string ckey) =>
            {
                var proOrFie = outType.GetProperties().Cast<MemberInfo>()
                    .Union(outType.GetFields().Cast<MemberInfo>())
                    .Union(inType.GetProperties().Cast<MemberInfo>())
                    .Union(inType.GetFields().Cast<MemberInfo>())
                    .GroupBy(i => i.Name).Where(i => i.Count() == 2)
                    .ToDictionary(i => i.Key, i => i);

                var returnTarget = Expression.Label(outType);
                var returnLabel = Expression.Label(returnTarget, Expression.Default(outType));
                var pramSource = Expression.Parameter(typeof(object));
                var parmConverted = Expression.Convert(pramSource, inType);
                var newExp = Expression.New(outType);
                var variate = Expression.Parameter(outType, "instance");
                var assVar = Expression.Assign(variate, newExp);

                Expression[] expressions = new Expression[proOrFie.Count + 3];
                expressions[0] = assVar;
                var assExps = proOrFie.Keys.Select(i =>
                    {
                        var value = Expression.PropertyOrField(parmConverted, i);
                        var prop = Expression.PropertyOrField(variate, i);
                        var assIgnExp = Expression.Assign(prop, value);
                        return assIgnExp;
                    });
                var index = 1;
                foreach (var exp in assExps)
                {
                    expressions[index] = exp;
                    index++;
                }

                expressions[index] = Expression.Return(returnTarget,variate);
                expressions[index + 1] = returnLabel;
                var block = Expression.Block(new[] { variate }, expressions);

                var expression = Expression.Lambda<Func<object, T>>(block, pramSource);
                return expression.Compile();
            });

            return func;
        }

        public static DataTransfer<T> Instance = new DataTransfer<T>();
    }
}

性能测试

首先,使用ExpressionTree在第一次调用的时候,会产生很是明显的性能开销。因此,在进行测试以前,都会对各个方法预调用一次,从而缓存ET(简写)的编译(不带引号)结果。同事,为了公平起见,对比的基于反射实现的代码页进行了略微的处理。

对比代码:

1.加载属性

() => ducks.Select(i => pro.GetValue(i)).ToArray()

2.更新属性

foreach (var duck in ducks) { pro.SetValue(duck, "AssignedReflection"); }

3.数据转换

public static Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> Analyze(Type ts, Type to)
        {
            var names = to.GetProperties()
               .Union(ts.GetProperties())
               .GroupBy(i => i.Name).Where(i => i.Count() == 2)
               .Select(i => i.Key);
            var result = ts.GetProperties()
                .Where(i => names.Contains(i.Name)).OrderBy(i => i.Name)
                .Zip(to.GetProperties().Where(i => names.Contains(i.Name)).OrderBy(i => i.Name), (a, b) => new { Key = a.Name, Item1 = a, Item2 = b })
                .ToDictionary(i => i.Key, i => new Tuple<PropertyInfo, PropertyInfo>(i.Item1, i.Item2));
            return result;
        }

        /// <summary>
        /// Item1:Source,Item2:Target
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="OutT"></typeparam>
        /// <param name="source"></param>
        /// <param name="refResult"></param>
        /// <returns></returns>
        public static IEnumerable<OutT> TransferByReflection<T, OutT>(IEnumerable<T> source,
            Dictionary<string, Tuple<PropertyInfo, PropertyInfo>> refResult) where OutT : new()
        {
            foreach (var sor in source)
            {
                var target = new OutT();
                foreach (var p in refResult)
                {
                    object value = p.Value.Item1.GetValue(sor, null);
                    p.Value.Item2.SetValue(target, value);
                }
                yield return target;
            }
        }

同时还和对应的硬编码进行了对比,这里就不贴代码了。如下是结果(Tick为时间单位(1/10000毫秒)):

ExpressionTree NativeCode Reflection ArrayLenth Benefits Action
26 12 21 10 -5 AccessProperty
49 24 90 100 41 AccessProperty
224 140 842 1000 618 AccessProperty
2201 1294 7807 10000 5606 AccessProperty
35884 14730 77336 100000 41452 AccessProperty
223865 138630 789335 1000000 565470 AccessProperty
           
ExpressionTree NativeCode Reflection ArrayLenth Benefits Action
14 5 17 10 3 AssignValue
23 10 101 100 78 AssignValue
109 45 983 1000 874 AssignValue
931 410 10542 10000 9611 AssignValue
9437 3720 116147 100000 106710 AssignValue
94895 45392 1064471 1000000 969576 AssignValue
           
ExpressionTree NativeCode Reflection ArrayLenth Benefits Action
47 11 11 10 -36 TransferData
101 46 744 100 643 TransferData
538 240 7004 1000 6466 TransferData
4945 2331 77758 10000 72813 TransferData
91831 23606 785472 100000 693641 TransferData
960657 681635 8022245 1000000 7061588 TransferData

同时,这里附上对应的图表:

由以上图表可知,当元素小于1000个的时候,收益不会超过1毫秒。因此,此时的改进空间能够说是几乎没有。固然,若是在整个项目中有不少地方,好比说...1000个地方,使用了相似的代码(反射),确实会致使性能问题。但这已经不是[反射]的错了。

原理和理解

上诉代码作的事情其实只有一件:“将逻辑缓存起来。”为何说是将逻辑缓存起来?这是由于我的以为,ET实际上是描述代码逻辑的一种手段,和编程语言差不到哪里去了。只不过它编译的时候没有语法解析这一步。因此和调用编译器动态编译相比,“性能更好”也更轻量(臆测)。

而“为何ET比反射快”,这个问题实际上应该是“为何反射比ET慢”。反射调用的时候,首先要搜索属性,而后进行操做的时候又要进行大量的类型转换和校验。而对于ET而言,这些操做会在第一次完成以后“固定”并缓存起来。

收获

经过这次探讨,咱们收获了什么呢?

1.只要当心一点,反射根本不是噩梦,让那些反射黑闭嘴。

2.就算反射致使了问题,咱们仍然有办法解决。从以上数据来看,耗时为硬编码的两倍左右。若是2X->∞,那X也好不到哪里去。

3.好用。经过这种方式,咱们能很是方便的访问“属性的属性”。好比:

 ducks.SelectPropertyOrField<Duck, object>("Name.Length")

4.更多可能性。咱们甚至能够在不产生太大的性能开销的状况下完成一个深拷贝组件,来实现必定程度上通用的深拷贝。

附件

这里附上我使用的代码,供你们参考。

http://pan.baidu.com/s/1c0dF3FE(OneDrive不给力)

相关文章
相关标签/搜索