.NET Core/Framework如何創(chuàng)建委托大幅度提高反射調(diào)用的性能詳解
前言
大家都知道反射傷性能,但不得不反射的時候又怎么辦呢?當(dāng)真的被問題逼迫的時候還是能找到解決辦法的。
反射是一種很重要的技術(shù),然而它與直接調(diào)用相比性能要慢很多,因此如何優(yōu)化反射性能也就成為一個不得不面對的問題。 目前最常見的優(yōu)化反射性能的方法就是采用委托:用委托的方式調(diào)用需要反射調(diào)用的方法(或者屬性、字段)。
為反射得到的方法創(chuàng)建一個委托,此后調(diào)用此委托將能夠提高近乎直接調(diào)用方法本身的性能。(當(dāng)然 Emit 也能夠幫助我們顯著提升性能,不過直接得到可以調(diào)用的委托不是更加方便嗎?)
性能對比數(shù)據(jù)

▲ 沒有什么能夠比數(shù)據(jù)更有說服力(注意后面兩行是有秒數(shù)的)
可能我還需要解釋一下那五行數(shù)據(jù)的含義:
- 直接調(diào)用(😏應(yīng)該沒有什么比直接調(diào)用函數(shù)本身更有性能優(yōu)勢的吧)
- 做一個跟直接調(diào)用的方法功能一模一樣的委托(😮目的是看看調(diào)用委托相比調(diào)用方法本身是否有性能損失,從數(shù)據(jù)上看,損失非常?。?/li>
- 本文重點 將反射出來的方法創(chuàng)建一個委托,然后調(diào)用這個委托(🤩看看吧,性能跟直接調(diào)差別也不大嘛)
- 先反射得到方法,然后一直調(diào)用這個方法(😥終于可以看出來反射本身還是挺傷性能的了,50 多倍的性能損失啊)
- 緩存都不用,從頭開始反射然后調(diào)用得到的方法(😒100 多倍的性能損失了)
以下是測試代碼,可以更好地理解上圖數(shù)據(jù)的含義:
using System;
using System.Diagnostics;
using System.Reflection;
namespace Walterlv.Demo
{
public class Program
{
static void Main(string[] args)
{
// 調(diào)用的目標實例。
var instance = new StubClass();
// 使用反射找到的方法。
var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) });
Assert.IsNotNull(method);
// 將反射找到的方法創(chuàng)建一個委托。
var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);
// 跟被測方法功能一樣的純委托。
Func<int, int> pureFunc = value => value;
// 測試次數(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)建出來的委托調(diào)用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = func(5);
}
watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射創(chuàng)建出來的委托調(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;
}
}
}
}
如何實現(xiàn)
實現(xiàn)的關(guān)鍵就在于 MethodInfo.CreateDelegate 方法。這是 .NET Standard 中就有的方法,這意味著 .NET Framework 和 .NET Core 中都可以使用。
此方法有兩個重載:
- 要求傳入一個類型,而這個類型就是應(yīng)該轉(zhuǎn)成的委托的類型
- 要求傳入一個類型和一個實例,一樣的,類型是應(yīng)該轉(zhuǎn)成的委托的類型
他們的區(qū)別在于前者創(chuàng)建出來的委托是直接調(diào)用那個實例方法本身,后者則更原始一些,真正調(diào)用的時候還需要傳入一個實例對象。
拿上面的 StubClass 來說明會更直觀一些:
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 里實例的方法其實都是后者,而前者更像 C# 中的代碼,容易理解。)
單獨使用 CreateDelegate 方法可能每次都需要嘗試第一個參數(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)用時就像 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)用時就像 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> —— 自動生成多個類型的泛型 - 呂毅 一文中寫了一個泛型生成器,可以稍加修改以便適應(yīng)這種泛型類。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
asp.net(c#)限制用戶輸入規(guī)定的字符和數(shù)字的代碼
這幾天在看到一個網(wǎng)站的注冊的時候,就只允許輸入規(guī)定的字符和數(shù)字。我就好奇的寫了一個校驗的代碼。呵呵 不知道對大家有沒有用。如果有用的話可以保存。沒有用就當(dāng)是看看以下了。2010-10-10
Asp.net mvc 權(quán)限過濾和單點登錄(禁止重復(fù)登錄)
這篇文章主要介紹了Asp.net mvc 權(quán)限過濾和單點登錄(禁止重復(fù)登錄)的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12
Windows虛擬主機與VPS如何實現(xiàn)301重定向(asp.net)
301重定向應(yīng)該是研究SEO必須掌握的技術(shù)。如果你是剛接觸SEO的菜鳥,想了解什么是301重定向,請看《html實現(xiàn)301重定向的方法》一文,我在該篇隨筆中引用了Google網(wǎng)站站長工具對301重定向的解釋2011-12-12

