Reflection與ExpressionTree的效能比較

Reflection與ExpressionTree的效能比較

今天看到黑大發的這篇 Reflection執行效能測試 文章

剛好最近有在玩ExpressionTree,所以也補上用ExpressionTree做對屬性賦值的效能比較

先貼上測試Code及測試結果

class Program
{
    static void Main(string[] args)
    {
        //因為沒有資料庫的資料來源,所以用集合模擬資料
        List<Dictionary<string, string>> listDictionary = InitializeTestData();

        Console.WriteLine("Row Count: {0:N0}", listDictionary.Count);

        Stopwatch sw = new Stopwatch();

        List<TestClass> list = new List<TestClass>();

        Console.WriteLine("==== HardCode Start====");
        sw.Reset();
        sw.Start();
        foreach (var row in listDictionary)
        {
            TestClass c = new TestClass();
            c.C1 = row["C1"];
            c.C2 = row["C2"];
            c.C3 = row["C3"];
            c.C4 = row["C4"];
            c.C5 = row["C5"];
            c.C6 = row["C6"];
            c.C7 = row["C7"];
            c.C8 = row["C8"];
            c.C9 = row["C9"];
            c.C10 = row["C10"];
            list.Add(c);
        }
        sw.Stop();
        Console.WriteLine("==== HardCode End====: {0:N0}", sw.ElapsedMilliseconds);

        //===================================================================

        list = new List<TestClass>();
        Console.WriteLine("==== Reflection Cache Version Start====");

        sw.Reset();
        sw.Start();
        foreach (var row in listDictionary)
        {
            TestClass c = new TestClass();
            foreach (var key in row.Keys)
            {
                ReflectionFactory.Set(c, row[key], key);
            }
            list.Add(c);
        }
        sw.Stop();
        Console.WriteLine("==== Reflection Cache Version End====: {0:N0}", sw.ElapsedMilliseconds);

        //=====================================================================

        list = new List<TestClass>();
        Console.WriteLine("==== ExpressionTree Start====");
        sw.Reset();
        sw.Start();

        foreach (var row in listDictionary)
        {
            TestClass c = new TestClass();

            foreach (var key in row.Keys)
            {
                ExpressionFactory.Set(c, row[key], key);
            }
            list.Add(c);
        }
        sw.Stop();
        Console.WriteLine("==== ExpressionTree End====: {0:N0}", sw.ElapsedMilliseconds);

        Console.ReadKey();


    }

    static List<Dictionary<string, string>> InitializeTestData()
    {
        List<Dictionary<string, string>> data = new List<Dictionary<string, string>>();

        for (int i = 0; i < 100000; i++)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            dic["C1"] = Guid.NewGuid().ToString();
            dic["C2"] = Guid.NewGuid().ToString();
            dic["C3"] = Guid.NewGuid().ToString();
            dic["C4"] = Guid.NewGuid().ToString();
            dic["C5"] = Guid.NewGuid().ToString();
            dic["C6"] = Guid.NewGuid().ToString();
            dic["C7"] = Guid.NewGuid().ToString();
            dic["C8"] = Guid.NewGuid().ToString();
            dic["C9"] = Guid.NewGuid().ToString();
            dic["C10"] = Guid.NewGuid().ToString();
            data.Add(dic);
        }
        return data;
    }
}

public class TestClass
{
    public string C1 { get; set; }
    public string C2 { get; set; }
    public string C3 { get; set; }
    public string C4 { get; set; }
    public string C5 { get; set; }
    public string C6 { get; set; }
    public string C7 { get; set; }
    public string C8 { get; set; }
    public string C9 { get; set; }
    public string C10 { get; set; }
}

 

測試結果

image

雖然跟直接對屬性塞值的效能還是有一段差距,但比起Reflection已經好一些了。

p.s 在這個測試中我有對 PropertyInfo 做 Cache

 

如果對這個結果還可以接受的話,接下來就來看看實作的方式吧

 

Reflection

public class ReflectionFactory
{
    //用來對 PropertyInfo 做 Cache
    public static Dictionary<PropertyKey, PropertyInfo> PropertyCache = new Dictionary<PropertyKey, PropertyInfo>();
    public static object lockObject = new object();

    public static void Set(object obj, object value, string propertyName)
    {
        PropertyKey key = new PropertyKey(obj.GetType(), propertyName);

        //檢查是否已存在此PropertyInfo
        if (!PropertyCache.ContainsKey(key))
        {
            lock (lockObject)
            {
                if (!PropertyCache.ContainsKey(key))
                {
                    //取得PropertyInfo
                    var propertyInfo = obj.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
                    //塞值
                    propertyInfo.SetValue(obj, value, null);
                    //存入Cache,下次就不用再做GetProperty的動作了
                    PropertyCache[key] = propertyInfo;
                    return;
                }
            }
        }
        PropertyCache[key].SetValue(obj, value, null);
    }
}

ExpressionTree

public class ExpressionFactory
{
    public static Dictionary<PropertyKey, Action<object, object>> PropertyCache = new Dictionary<PropertyKey, Action<object, object>>();
    public static object lockObject = new object();


    public static void Set(object obj,object value, string propertyName)
    {
        PropertyKey key = new PropertyKey(obj.GetType(), propertyName);

        //先檢查有沒有該屬性的委派方法(塞值)
        if (!PropertyCache.ContainsKey(key))
        {
            lock (lockObject)
            {
                if (!PropertyCache.ContainsKey(key))
                {
                    //沒有的話,就建立利用ExpressionTree建立委派
                    var action = CreateSetAction(obj, propertyName);
                    //放入Cache
                    PropertyCache[key] = action;
                    //執行賦值動作
                    action(obj, value);
                    return;
                }
            }
        }
        PropertyCache[key](obj,value);
    }

    private static Action<object, object> CreateSetAction(object obj, string propertyName)
    {
        var propertyInfo = obj.GetType().GetProperty(propertyName);

        //參數1 目標物件
        var targetObj = Expression.Parameter(typeof(object), "obj");
        //參數2 值
        var propertyValue = Expression.Parameter(typeof(object), "value");

        //主體
        //第一個參數:由於上方參數1是object型別,因此先轉成目標物件型別
        //第二個參數:Set Property 的方法主體
        //第三個參數:上方的參數2是object型別,因此要轉成目標屬性型別
        var setMethod = Expression.Call( 
                      Expression.Convert(targetObj, obj.GetType()),
                      propertyInfo.GetSetMethod(),
                      Expression.Convert(propertyValue, 
                                         propertyInfo.PropertyType)
                      );

        //將此 ExpressionTree 編譯成 Lambda,沒有回傳值,兩個參數的委派方法
        return Expression.Lambda<Action<object, object>>(setMethod, targetObj, propertyValue).Compile();
    }
}

另外Cache的實作方式是把我要的東西放在一個靜態集合裡面

key用Type及名稱建立一個PropertyKey物件,並override GetGashCode及Equals

public class PropertyKey
{

    public Type PropertyType { get; private set; }
    public string PropertyName { get; private set; }

    public PropertyKey(Type type, string propertyName)
    {
        PropertyType = type;
        PropertyName = propertyName;
    }

    public override int GetHashCode()
    {
        return PropertyType.GetHashCode() ^ PropertyName.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return this.GetHashCode().Equals(obj.GetHashCode());
    }
}

這樣對同一個Type的屬性做操作時,可以不用重複做一些耗效能的動作

(當然這是在大量重複利用的情況下才看得出效果)

這邊Expression的用法是針對某個Type的某個屬性,產生一個委派方法

例如 TestClass的C1屬性,在跑過CreateSetAction之後,會產生一個好比

 

Action<object, object> action = SetC1;

public void SetC1(object obj,object value)
{
    ((TestClass)obj).C1 = (string)value;
}

這樣的東西出來(以上只是示意,實際上不會完全一樣,但就是一個匿名的委派)

因此可以直接用 action(實體,值) 這樣去塞值進去,並且將這個匿名委派Cache起來,

以便可以重複使用。

 

大概是這個樣子,上面的Code是我打算拿來當作自己的Library用的,目前是第一版

可能有些地方還沒有考慮到或不夠周詳,再慢慢改進中...

原始碼下載