Reflection執行效能測試
4 | 16,773 |
在寫類似Code Generator的功能,遇到一個抉擇點:
若要將DataRow的各欄位逐一映對到資料物件的各欄位上,該使用Reflection還是Hard-Coding?
使用Reflection方式(PropertyInfo)讀寫物件屬性可大幅簡化程式碼複雜度,但需付出效能上的代價。如先前文章所提,隨著CPU的運算能力愈來愈強大,效能代價的負面影響也會愈來愈小。只是在查詢資料過程中,DataRow轉換成資料物件的程序會被連續大量執行,恐怕又得另當別論。例如: 查詢取回10萬筆DataRow,接著就得進行10萬次DataRow轉物件的動作,若每個DataRow有30個欄位,PropertyInfo.SetValue就會被呼叫300萬次,這個量已讓效能差異不容忽視。
於是,我寫了一段簡單的Benchmark,由資料庫查詢取得約兩萬筆資料,每筆有24個欄位。在C#裡宣告有24個同名屬性的類別用以存放查詢一筆結果,分別使用Reflection PropertyInfo.SetValue及ObjectVariable.PropName = (T) row[“PropName”]的Hard-Coding法將DataRow的內容逐一轉成資料物件放入集合,並以Stopwatch計時,反覆跑10次進行測試。
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OracleClient;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static string cs = "Data Source=BOO;User Id=foo;Password=blabubu";
static void Main(string[] args)
{
using (var cn = new OracleConnection(cs))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = "SELECT * FROM MY_TABLE";
DataTable t = new DataTable();
t.Load(cmd.ExecuteReader());
cn.Close();
List<MyCustClass> lst;
Console.WriteLine("Row Count: {0:N0}", t.Rows.Count);
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("==== Test[{0}] ====", i);
lst = new List<MyCustClass>();
sw.Reset();
sw.Start();
foreach (DataRow row in t.Rows)
lst.Add(MyCustClass.RefConv(row));
sw.Stop();
Console.WriteLine(" * By Reflection: {0:N0}", sw.ElapsedMilliseconds);
lst = new List<MyCustClass>();
sw.Reset();
sw.Start();
foreach (DataRow row in t.Rows)
lst.Add(MyCustClass.ExplicitConv(row));
Console.WriteLine(" * By Hard-Coding: {0:N0}", sw.ElapsedMilliseconds);
}
}
Console.Read();
}
}
public class MyCustClass
{
public string C1 { get; set; }
public DateTime C2 { get; set; }
public string C3 { get; set; }
public DateTime 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; }
public string C11 { get; set; }
public DateTime C12 { get; set; }
public string C13 { get; set; }
public decimal C14 { get; set; }
public decimal C15 { get; set; }
public decimal C16 { get; set; }
public decimal C17 { get; set; }
public decimal C18 { get; set; }
public decimal C19 { get; set; }
public decimal C20 { get; set; }
public decimal C21 { get; set; }
public decimal C22 { get; set; }
public string C23 { get; set; }
public string C24 { get; set; }
public static MyCustClass RefConv(DataRow row)
{
MyCustClass mcc = new MyCustClass();
foreach (PropertyInfo pi in mcc.GetType().GetProperties())
pi.SetValue(mcc, row[pi.Name], null);
return mcc;
}
public static MyCustClass ExplicitConv(DataRow row)
{
MyCustClass mcc = new MyCustClass();
mcc.C1 = (string)row["C1"];
mcc.C2 = (DateTime)row["C2"];
mcc.C3 = (string)row["C3"];
mcc.C4 = (DateTime)row["C4"];
mcc.C5 = (string)row["C5"];
//...省略...
mcc.C22 = (decimal)row["C22"];
mcc.C23 = (string)row["C23"];
mcc.C24 = (string)row["C24"];
return mcc;
}
}
}
程式結果出爐,直接寫死mcc.C1 = (string)row[“C1”]比起C1 PropertyInfo.SetValue()快了約10倍!!
Row Count: 19,429 ==== Test[0] ==== * By Reflection: 721 * By Hard-Coding: 60 ==== Test[1] ==== * By Reflection: 788 * By Hard-Coding: 60 ==== Test[2] ==== * By Reflection: 637 * By Hard-Coding: 69 ==== Test[3] ==== * By Reflection: 1,077 * By Hard-Coding: 99 ==== Test[4] ==== * By Reflection: 1,009 * By Hard-Coding: 75 ==== Test[5] ==== * By Reflection: 652 * By Hard-Coding: 53 ==== Test[6] ==== * By Reflection: 622 * By Hard-Coding: 51 ==== Test[7] ==== * By Reflection: 639 * By Hard-Coding: 47 ==== Test[8] ==== * By Reflection: 667 * By Hard-Coding: 63 ==== Test[9] ==== * By Reflection: 846 * By Hard-Coding: 73
最後的結論是,既然透過Code Generator預先產生好資料值設定的程式碼並不會增加太多成本,卻有高達10倍的效能提升,這個測試算是給了肯定的決策佐證。未來在玩自動產生程式碼把戲時,還是別偷雞,乖乖地逐一展開各屬性的映對程式碼爭取效能才是王道。
[2011-07-07更新] Jerry補充了Expression Tree做法的測試,laneser則實做了Delegate.CreateDelegate(見留言),大家一起完成了PropertyInfo、ExpressionTree、Delegate.CreateDelegate測試大三元,依結果來看,排除Hardcoding,重複執行效率由Delegate.CreateDelegate勝出。(laneser提供的另一篇文章也有相似結論)
[2011-07-07更新] iceboundrock再補充了一個很酷的Open Source Library(http://fastreflectionlib.codeplex.com/),直接擴充了PropertyInfo及MethodInfo,加上高速版的FastGetValue() Method,很棒的點子,也非常直覺易用,謝謝iceboundrock的分享。
Comments
# by laneser
public class MyCustClass { public string C1 { get; set; } public DateTime C2 { get; set; } public string C3 { get; set; } public DateTime 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; } public string C11 { get; set; } public DateTime C12 { get; set; } public string C13 { get; set; } public decimal C14 { get; set; } public decimal C15 { get; set; } public decimal C16 { get; set; } public decimal C17 { get; set; } public decimal C18 { get; set; } public decimal C19 { get; set; } public decimal C20 { get; set; } public decimal C21 { get; set; } public decimal C22 { get; set; } public string C23 { get; set; } public string C24 { get; set; } private static Dictionary<string, Action<MyCustClass,object>> SetMethods = createSetMethods(); static Action<T, object> CreateSetMethodHelper<T, TParam>(MethodInfo method) { var act = (Action<T, TParam>)Delegate.CreateDelegate(typeof(Action<T, TParam>), method); Action<T, object> ret = (T target, object param) => act(target, (TParam)param); return ret; } private static Dictionary<string, Action<MyCustClass,object>> createSetMethods() { var properties = typeof(MyCustClass).GetProperties(); var r = new Dictionary<string, Action<MyCustClass, object>>(properties.Length); foreach (var p in properties) { var method = p.GetSetMethod(); var genericHelper = typeof(MyCustClass).GetMethod("CreateSetMethodHelper", BindingFlags.Static | BindingFlags.NonPublic); var constructedHelper = genericHelper.MakeGenericMethod(typeof(MyCustClass), method.GetParameters()[0].ParameterType); r[p.Name] = (Action<MyCustClass, object>)constructedHelper.Invoke(null, new object[] {method}); } return r; } public static MyCustClass RefConv(DataRow row) { MyCustClass mcc = new MyCustClass(); foreach (var setfuncpair in SetMethods) setfuncpair.Value(mcc, row[setfuncpair.Key]); return mcc; } public static MyCustClass ExplicitConv(DataRow row) { MyCustClass mcc = new MyCustClass(); mcc.C1 = (string)row["C1"]; mcc.C2 = (DateTime)row["C2"]; mcc.C3 = (string)row["C3"]; mcc.C4 = (DateTime)row["C4"]; mcc.C5 = (string)row["C5"]; mcc.C6 = (string)row["C6"]; mcc.C7 = (string)row["C7"]; mcc.C8 = (string)row["C8"]; mcc.C9 = (string)row["C9"]; mcc.C10 = (string)row["C10"]; mcc.C11 = (string)row["C11"]; mcc.C12 = (DateTime)row["C12"]; mcc.C13 = (string)row["C13"]; mcc.C14 = (decimal)row["C14"]; mcc.C15 = (decimal)row["C15"]; mcc.C16 = (decimal)row["C16"]; mcc.C17 = (decimal)row["C17"]; mcc.C18 = (decimal)row["C18"]; mcc.C19 = (decimal)row["C19"]; mcc.C20 = (decimal)row["C20"]; mcc.C21 = (decimal)row["C21"]; mcc.C22 = (decimal)row["C22"]; mcc.C23 = (string)row["C23"]; mcc.C24 = (string)row["C24"]; return mcc; } } ---- 這樣速度快了不少, ==== Test[0] ==== * By Reflection: 111 * By Hard-Coding: 56 ==== Test[1] ==== * By Reflection: 97 * By Hard-Coding: 64 ==== Test[2] ==== * By Reflection: 104 * By Hard-Coding: 81 ==== Test[3] ==== * By Reflection: 123 * By Hard-Coding: 55 ==== Test[4] ==== * By Reflection: 87 * By Hard-Coding: 57 ==== Test[5] ==== * By Reflection: 103 * By Hard-Coding: 53 ==== Test[6] ==== * By Reflection: 116 * By Hard-Coding: 42 ==== Test[7] ==== * By Reflection: 102 * By Hard-Coding: 61 ==== Test[8] ==== * By Reflection: 108 * By Hard-Coding: 44 ==== Test[9] ==== * By Reflection: 108 * By Hard-Coding: 53 可以參考 http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx
# by Jeffrey
to laneser, 謝謝分享Delegate.CreateDelegate的做法,再加上Jerry的測試(http://www.dotblogs.com.tw/lastsecret/archive/2011/07/07/31236.aspx) 大家合作完成PropertyInfo + ExpressionTree + Delegate.CreateDelegate 的測試三大元了。看來也跟您提的文章結論相同(http://www.cnblogs.com/artech/archive/2011/03/26/Propertyaccesstest.html) ,三者中CreateDelegate 效能最佳。 又一篇討論與迴響比本文精彩的文章,很高興我是抛磚頭的人!
# by iceboundrock
hi, you may want to check out this project: http://fastreflectionlib.codeplex.com/
# by Jeffrey
to iceboundrock, Awesome! 感謝分享~~