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

缘起

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

反射

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

关于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.根据属性名更新上诉对象的元素的属性/字段值。

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

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

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不给力)

原文地址:https://www.cnblogs.com/lightluomeng/p/4375713.html