方法的直接调用,反射调用与……Lambda表达式调用
方法的直接調(diào)用,反射調(diào)用與……Lambda表達式調(diào)用
2008-11-24 09:59 by Jeffrey Zhao, 32557 閱讀,?100?評論,?收藏,?編輯想調(diào)用一個方法很容易,直接代碼調(diào)用就行,這人人都會。其次呢,還可以使用反射。不過通過反射調(diào)用的性能會遠遠低于直接調(diào)用——至少從絕對時間上來看的確是這樣。雖然這是個眾所周知的現(xiàn)象,我們還是來寫個程序來驗證一下。比如我們現(xiàn)在新建一個Console應用程序,編寫一個最簡單的Call方法。
class Program {static void Main(string[] args){}public void Call(object o1, object o2, object o3) { } }Call方法接受三個object參數(shù)卻沒有任何實現(xiàn),這樣我們就可以讓測試專注于方法調(diào)用,而并非方法實現(xiàn)本身。于是我們開始編寫測試代碼,比較一下方法的直接調(diào)用與反射調(diào)用的性能差距:
static void Main(string[] args) {int times = 1000000;Program program = new Program();object[] parameters = new object[] { new object(), new object(), new object() };program.Call(null, null, null); // force JIT-compileStopwatch watch1 = new Stopwatch();watch1.Start();for (int i = 0; i < times; i++){program.Call(parameters[0], parameters[1], parameters[2]);}watch1.Stop();Console.WriteLine(watch1.Elapsed + " (Directly invoke)");MethodInfo methodInfo = typeof(Program).GetMethod("Call");Stopwatch watch2 = new Stopwatch();watch2.Start();for (int i = 0; i < times; i++){methodInfo.Invoke(program, parameters);}watch2.Stop();Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");Console.WriteLine("Press any key to continue...");Console.ReadKey(); }執(zhí)行結(jié)果如下:
00:00:00.0119041 (Directly invoke) 00:00:04.5527141 (Reflection invoke) Press any key to continue...通過各調(diào)用一百萬次所花時間來看,兩者在性能上具有數(shù)量級的差距。因此,很多框架在必須利用到反射的場景中,都會設法使用一些較高級的替代方案來改善性能。例如,使用CodeDom生成代碼并動態(tài)編譯,或者使用Emit來直接編寫IL。不過自從.NET 3.5發(fā)布了Expression相關的新特性,我們在以上的情況下又有了更方便并直觀的解決方案。
了解Expression相關特性的朋友可能知道,System.Linq.Expressions.Expression<TDelegate>類型的對象在調(diào)用了它了Compile方法之后將得到一個TDelegate類型的委托對象,而調(diào)用一個委托對象與直接調(diào)用一個方法的性能開銷相差無幾。那么對于上面的情況,我們又該得到什么樣的Delegate對象呢?為了使解決方案足夠通用,我們必須將各種簽名的方法統(tǒng)一至同樣的委托類型中,如下:
public Func<object, object[], object> GetVoidDelegate() {Expression<Action<object, object[]>> exp = (instance, parameters) => ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);Action<object, object[]> action = exp.Compile();return (instance, parameters) =>{action(instance, parameters);return null;}; }如上,我們就得到了一個Func<object, object[], object>類型的委托,這意味它接受一個object類型與object[]類型的參數(shù),以及返回一個object類型的結(jié)果——等等,朋友們有沒有發(fā)現(xiàn),這個簽名與MethodInfo類型的Invoke方法完全一致?不過可喜可賀的是,我們現(xiàn)在調(diào)用這個委托的性能遠高于通過反射來調(diào)用了。那么對于有返回值的方法呢?那構造一個委托對象就更方便了:
public int Call(object o1, object o2) { return 0; }public Func<object, object[], object> GetDelegate() {Expression<Func<object, object[], object>> exp = (instance, parameters) =>((Program)instance).Call(parameters[0], parameters[1]);return exp.Compile(); }至此,我想朋友們也已經(jīng)能夠輕松得出調(diào)用靜態(tài)方法的委托構造方式了。可見,這個解決方案的關鍵在于構造一個合適的Expression<TDelegate>,那么我們現(xiàn)在就來編寫一個DynamicExecuter類來作為一個較為完整的解決方案:
public class DynamicMethodExecutor {private Func<object, object[], object> m_execute;public DynamicMethodExecutor(MethodInfo methodInfo){this.m_execute = this.GetExecuteDelegate(methodInfo);}public object Execute(object instance, object[] parameters){return this.m_execute(instance, parameters);}private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo){// parameters to executeParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");// build parameter listList<Expression> parameterExpressions = new List<Expression>();ParameterInfo[] paramInfos = methodInfo.GetParameters();for (int i = 0; i < paramInfos.Length; i++){// (Ti)parameters[i]BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);parameterExpressions.Add(valueCast);}// non-instance for static method, or ((TInstance)instance)Expression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceParameter, methodInfo.ReflectedType);// static invoke or ((TInstance)instance).MethodMethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);// ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)if (methodCall.Type == typeof(void)){Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);Action<object, object[]> execute = lambda.Compile();return (instance, parameters) =>{execute(instance, parameters);return null;};}else{UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceParameter, parametersParameter);return lambda.Compile();}} }DynamicMethodExecutor的關鍵就在于GetExecuteDelegate方法中構造Expression Tree的邏輯。如果您對于一個Expression Tree的結(jié)構不太了解的話,不妨嘗試一下使用Expression Tree Visualizer來對一個現(xiàn)成的Expression Tree進行觀察和分析。我們將一個MethodInfo對象傳入DynamicMethodExecutor的構造函數(shù)之后,就能將各組不同的實例對象和參數(shù)對象數(shù)組傳入Execute進行執(zhí)行。這一切就像使用反射來進行調(diào)用一般,不過它的性能就有了明顯的提高。例如我們添加更多的測試代碼:
DynamicMethodExecutor executor = new DynamicMethodExecutor(methodInfo); Stopwatch watch3 = new Stopwatch(); watch3.Start(); for (int i = 0; i < times; i++) {executor.Execute(program, parameters); } watch3.Stop(); Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");現(xiàn)在的執(zhí)行結(jié)果則是:
00:00:00.0125539 (Directly invoke) 00:00:04.5349626 (Reflection invoke) 00:00:00.0322555 (Dynamic executor) Press any key to continue...事實上,Expression<TDelegate>類型的Compile方法正是使用Emit來生成委托對象。不過現(xiàn)在我們已經(jīng)無需將目光放在更低端的IL上,只要使用高端的API來進行Expression Tree的構造,這無疑是一種進步。不過這種方法也有一定局限性,例如我們只能對公有方法進行調(diào)用,并且包含out/ref參數(shù)的方法,或者除了方法外的其他類型成員,我們就無法如上例般愜意地編寫代碼了。
補充
木野狐兄在評論中引用了Code Project的文章《A General Fast Method Invoker》,其中通過Emit構建了FastInvokeHandler委托對象(其簽名與Func<object, object[], object>完全相同)的調(diào)用效率似乎較“方法直接”調(diào)用的性能更高(雖然從原文示例看來并非如此)。事實上FastInvokeHandler其內(nèi)部實現(xiàn)與DynamicMethodExecutor完全相同,居然有如此令人不可思議的表現(xiàn)實在讓人嘖嘖稱奇。我猜測,FastInvokeHandler與DynamicMethodExecutor的性能優(yōu)勢可能體現(xiàn)在以下幾個方面:
不知道是否有對此感興趣的朋友能夠再做一個測試,不過請注意此類性能測試一定需要在Release編譯下進行(這點很容易被忽視),否則意義其實不大。
此外,我還想強調(diào)的就是,本篇文章進行是純技術上的比較,并非在引導大家追求點滴性能上的優(yōu)化。有時候看到一些關于比較for或foreach性能優(yōu)劣的文章讓許多朋友都糾結(jié)與此,甚至搞得面紅耳赤,我總會覺得有些無可奈何。其實從理論上來說,提高性能的方式有許許多多,記得當時在大學里學習Introduction to Computer System這門課時得一個作業(yè)就是為一段C程序作性能優(yōu)化,當時用到不少手段,例如內(nèi)聯(lián)方法調(diào)用以減少CPU指令調(diào)用次數(shù)、調(diào)整循環(huán)嵌套順序以提高CPU緩存命中率,將一些代碼使用內(nèi)嵌ASM替換等等,可謂“無所不用其極”,大家都在為幾個時鐘周期的性能提高而發(fā)奮圖強歡呼雀躍……
那是理論,是在學習。但是在實際運用中,我們還必須正確對待學到的理論知識。我經(jīng)常說的一句話是:“任何應用程序都會有其性能瓶頸,只有從性能瓶頸著手才能做到事半功倍的結(jié)果。”例如,普通Web應用的性能瓶頸往往在外部IO(尤其是數(shù)據(jù)庫讀寫),要真正提高性能必須從此入手(例如數(shù)據(jù)庫調(diào)優(yōu),更好的緩存設計)。正因如此,開發(fā)一個高性能的Web應用程序的關鍵不會在語言或語言運行環(huán)境上,.NET、RoR、PHP、Java等等在這一領域都表現(xiàn)良好。
分類:?05. 實踐優(yōu)化,?01. DotNet框架 標簽:?性能,?Lambda表達式,?反射,?Emit 綠色通道:?好文要頂?關注我?收藏該文與我聯(lián)系? 8 0 (請您對文章做出評價) ??博主前一篇:在Web應用程序開發(fā)過程中利用ASP.NET MVC框架的實戰(zhàn)技巧??博主后一篇:和諧社區(qū),和諧技術:微軟的寵兒們,為什么富人的孩子就不能早當家?
本文基于署名 2.5 中國大陸許可協(xié)議發(fā)布,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名趙劼(包含鏈接),具體操作方式可參考此處。如您有任何疑問或者授權方面的協(xié)商,請給我留言。
?
原文鏈接:http://www.cnblogs.com/jeffreyzhao/archive/2008/11/24/invoke-method-by-lambda-expression.html
轉(zhuǎn)載于:https://www.cnblogs.com/ppcompany/articles/2713484.html
總結(jié)
以上是生活随笔為你收集整理的方法的直接调用,反射调用与……Lambda表达式调用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VNC 远程控制工具软件
- 下一篇: 陶哲轩实分析 引理8.2.7 注