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

相關文章
相關標籤/搜索