.NET Core/Framework如何創(chuàng)建委托大幅度提高反射調(diào)用的性能詳解
前言
大家都知道反射傷性能,但不得不反射的時(shí)候又怎么辦呢?當(dāng)真的被問題逼迫的時(shí)候還是能找到解決辦法的。
反射是一種很重要的技術(shù),然而它與直接調(diào)用相比性能要慢很多,因此如何優(yōu)化反射性能也就成為一個(gè)不得不面對(duì)的問題。 目前最常見的優(yōu)化反射性能的方法就是采用委托:用委托的方式調(diào)用需要反射調(diào)用的方法(或者屬性、字段)。
為反射得到的方法創(chuàng)建一個(gè)委托,此后調(diào)用此委托將能夠提高近乎直接調(diào)用方法本身的性能。(當(dāng)然 Emit 也能夠幫助我們顯著提升性能,不過直接得到可以調(diào)用的委托不是更加方便嗎?)
性能對(duì)比數(shù)據(jù)
▲ 沒有什么能夠比數(shù)據(jù)更有說(shuō)服力(注意后面兩行是有秒數(shù)的)
可能我還需要解釋一下那五行數(shù)據(jù)的含義:
- 直接調(diào)用(😏應(yīng)該沒有什么比直接調(diào)用函數(shù)本身更有性能優(yōu)勢(shì)的吧)
- 做一個(gè)跟直接調(diào)用的方法功能一模一樣的委托(😮目的是看看調(diào)用委托相比調(diào)用方法本身是否有性能損失,從數(shù)據(jù)上看,損失非常?。?/li>
- 本文重點(diǎn) 將反射出來(lái)的方法創(chuàng)建一個(gè)委托,然后調(diào)用這個(gè)委托(🤩看看吧,性能跟直接調(diào)差別也不大嘛)
- 先反射得到方法,然后一直調(diào)用這個(gè)方法(😥終于可以看出來(lái)反射本身還是挺傷性能的了,50 多倍的性能損失?。?/li>
- 緩存都不用,從頭開始反射然后調(diào)用得到的方法(😒100 多倍的性能損失了)
以下是測(cè)試代碼,可以更好地理解上圖數(shù)據(jù)的含義:
using System; using System.Diagnostics; using System.Reflection; namespace Walterlv.Demo { public class Program { static void Main(string[] args) { // 調(diào)用的目標(biāo)實(shí)例。 var instance = new StubClass(); // 使用反射找到的方法。 var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) }); Assert.IsNotNull(method); // 將反射找到的方法創(chuàng)建一個(gè)委托。 var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method); // 跟被測(cè)方法功能一樣的純委托。 Func<int, int> pureFunc = value => value; // 測(cè)試次數(shù)。 var count = 10000000; // 直接調(diào)用。 var watch = new Stopwatch(); watch.Start(); for (var i = 0; i < count; i++) { var result = instance.Test(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接調(diào)用"); // 使用同樣功能的 Func 調(diào)用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = pureFunc(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同樣功能的 Func 調(diào)用"); // 使用反射創(chuàng)建出來(lái)的委托調(diào)用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = func(5); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射創(chuàng)建出來(lái)的委托調(diào)用"); // 使用反射得到的方法緩存調(diào)用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = method.Invoke(instance, new object[] { 5 }); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法緩存調(diào)用"); // 直接使用反射調(diào)用。 watch.Restart(); for (var i = 0; i < count; i++) { var result = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) }) ?.Invoke(instance, new object[] { 5 }); } watch.Stop(); Console.WriteLine($"{watch.Elapsed} - {count} 次 - 直接使用反射調(diào)用"); } private class StubClass { public int Test(int i) { return i; } } } }
如何實(shí)現(xiàn)
實(shí)現(xiàn)的關(guān)鍵就在于 MethodInfo.CreateDelegate
方法。這是 .NET Standard 中就有的方法,這意味著 .NET Framework 和 .NET Core 中都可以使用。
此方法有兩個(gè)重載:
- 要求傳入一個(gè)類型,而這個(gè)類型就是應(yīng)該轉(zhuǎn)成的委托的類型
- 要求傳入一個(gè)類型和一個(gè)實(shí)例,一樣的,類型是應(yīng)該轉(zhuǎn)成的委托的類型
他們的區(qū)別在于前者創(chuàng)建出來(lái)的委托是直接調(diào)用那個(gè)實(shí)例方法本身,后者則更原始一些,真正調(diào)用的時(shí)候還需要傳入一個(gè)實(shí)例對(duì)象。
拿上面的 StubClass 來(lái)說(shuō)明會(huì)更直觀一些:
private class StubClass { public int Test(int i) { return i; } }
前者得到的委托相當(dāng)于 int Test(int i)
方法,后者得到的委托相當(dāng)于 int Test(StubClass instance, int i)
方法。(在 IL 里實(shí)例的方法其實(shí)都是后者,而前者更像 C# 中的代碼,容易理解。)
單獨(dú)使用 CreateDelegate 方法可能每次都需要嘗試第一個(gè)參數(shù)到底應(yīng)該傳入些什么,于是我將其封裝成了泛型版本,增加易用性。
using System; using System.Linq; using System.Reflection; using System.Diagnostics.Contracts; namespace Walterlv.Demo { public static class InstanceMethodBuilder<T, TReturnValue> { /// <summary> /// 調(diào)用時(shí)就像 var result = func(t)。 /// </summary> [Pure] public static Func<T, TReturnValue> CreateInstanceMethod<TInstanceType>(TInstanceType instance, MethodInfo method) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (method == null) throw new ArgumentNullException(nameof(method)); return (Func<T, TReturnValue>) method.CreateDelegate(typeof(Func<T, TReturnValue>), instance); } /// <summary> /// 調(diào)用時(shí)就像 var result = func(this, t)。 /// </summary> [Pure] public static Func<TInstanceType, T, TReturnValue> CreateMethod<TInstanceType>(MethodInfo method) { if (method == null) throw new ArgumentNullException(nameof(method)); return (Func<TInstanceType, T, TReturnValue>) method.CreateDelegate(typeof(Func<TInstanceType, T, TReturnValue>)); } } }
泛型的多參數(shù)版本可以使用泛型類型生成器生成,我在 生成代碼,從 <T> 到 <T1, T2, Tn> —— 自動(dòng)生成多個(gè)類型的泛型 - 呂毅 一文中寫了一個(gè)泛型生成器,可以稍加修改以便適應(yīng)這種泛型類。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
ASP.NET WebForm中<%=%>與<%#%>的區(qū)別
這篇文章主要介紹了ASP.NET WebForm中<%=%>與<%#%>的區(qū)別,需要的朋友可以參考下2015-01-01引用母版頁(yè)后在page頁(yè)面修改母版頁(yè)控件的值的方法
引用母版頁(yè)后在page頁(yè)面修改母版頁(yè)控件的值的方法,需要的朋友可以參考一下2013-03-03Linq to SQL Delete時(shí)遇到問題的解決方法
在Linq to SQL中要?jiǎng)h除一行記錄,官方的例子教我這樣做2008-03-03ASP.NET中操作數(shù)據(jù)庫(kù)的基本步驟分享
ASP.NET中操作數(shù)據(jù)庫(kù)的基本步驟分享,學(xué)習(xí)asp.net的朋友可以參考下。2011-10-10在ASP.NET Core中用HttpClient發(fā)送POST, PUT和DELETE請(qǐng)求
這篇文章主要介紹了在ASP.NET Core中用HttpClient發(fā)送POST, PUT和DELETE請(qǐng)求的方法,幫助大家更好的理解和學(xué)習(xí)使用ASP.NET Core,感興趣的朋友可以了解下2021-03-03Asp.net 連接MySQL的實(shí)現(xiàn)代碼[]
ASP.NET連接MySQL需要一個(gè)組件(.net本身不提供訪問MySQL的驅(qū)動(dòng))MySQL.Data.Dll,此為官方提供(純C#開發(fā),開源噢),有多個(gè)版本選擇,采用的數(shù)據(jù)訪問模式為ADO.NET,跟asp.net訪問sqlserver很像,非常簡(jiǎn)單。2009-08-08ASP.NET Core 3.x 并發(fā)限制的實(shí)現(xiàn)代碼
這篇文章主要介紹了ASP.NET Core 3.x 并發(fā)限制的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11asp.net gridview的Rowcommand命令中獲取行索引的方法總結(jié)
asp.net gridview的Rowcommand命令中獲取行索引的方法總結(jié),需要的朋友可以參考下。2010-05-05