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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM