.NET Core/Framework 创建委托以大幅度提高反射调用的性能
都知道反射傷性能,但不得不反射的時候又怎么辦呢?當真的被問題逼迫的時候還是能找到解決辦法的。
為反射得到的方法創建一個委托,此后調用此委托將能夠提高近乎直接調用方法本身的性能。(當然?Emit?也能夠幫助我們顯著提升性能,不過直接得到可以調用的委托不是更加方便嗎?)
性能對比數據
▲ 沒有什么能夠比數據更有說服力(注意后面兩行是有秒數的)
可能我還需要解釋一下那五行數據的含義:
直接調用(?應該沒有什么比直接調用函數本身更有性能優勢的吧)
做一個跟直接調用的方法功能一模一樣的委托(?目的是看看調用委托相比調用方法本身是否有性能損失,從數據上看,損失非常小)
本文重點?將反射出來的方法創建一個委托,然后調用這個委托(?看看吧,性能跟直接調差別也不大嘛)
先反射得到方法,然后一直調用這個方法(?終于可以看出來反射本身還是挺傷性能的了,50 多倍的性能損失啊)
緩存都不用,從頭開始反射然后調用得到的方法(?100 多倍的性能損失了)
以下是測試代碼,可以更好地理解上圖數據的含義:
using System;using System.Diagnostics;
using System.Reflection;
namespace Walterlv.Demo
{
public class Program
{
static void Main(string[] args)
{
????????????//?調用的目標實例。
var instance = new StubClass();
????????????//?使用反射找到的方法。
var method = typeof(StubClass).GetMethod(nameof(StubClass.Test), new[] { typeof(int) });
????????????//?將反射找到的方法創建一個委托。
var func = InstanceMethodBuilder<int, int>.CreateInstanceMethod(instance, method);
????????????//?跟被測方法功能一樣的純委托。
Func<int, int> pureFunc = value => value;
????????????//?測試次數。
var count = 10000000;
????????????//?直接調用。
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} 次 - 直接調用");
????????????//?使用同樣功能的?Func?調用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = pureFunc(5);
}
watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用同樣功能的 Func 調用");
????????????//?使用反射創建出來的委托調用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = func(5);
}
watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射創建出來的委托調用");
????????????//?使用反射得到的方法緩存調用。
watch.Restart();
for (var i = 0; i < count; i++)
{
var result = method.Invoke(instance, new object[] { 5 });
}
watch.Stop();
Console.WriteLine($"{watch.Elapsed} - {count} 次 - 使用反射得到的方法緩存調用");
????????????//?直接使用反射調用。
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} 次 - 直接使用反射調用");
}
private class StubClass
{
public int Test(int i)
{
return i;
}
}
}
}
上面的代碼中,有一個我們還沒有實現的?InstanceMethodBuilder?類型,接下來將介紹如何實現它。
如何實現
實現的關鍵就在于?MethodInfo.CreateDelegate?方法。這是?.NET?Standard?中就有的方法,這意味著?.NET?Framework?和?.NET?Core?中都可以使用。
此方法有兩個重載:
要求傳入一個類型,而這個類型就是應該轉成的委托的類型
要求傳入一個類型和一個實例,一樣的,類型是應該轉成的委托的類型
他們的區別在于前者創建出來的委托是直接調用那個實例方法本身,后者則更原始一些,真正調用的時候還需要傳入一個實例對象。
拿上面的?StubClass?來說明會更直觀一些:
private class StubClass{
public int Test(int i)
{
return i;
}
}
前者得到的委托相當于?int?Test(int?i)?方法,后者得到的委托相當于?int?Test(StubClass?instance,?int?i)?方法。(在?IL?里實例的方法其實都是后者,而前者更像?C#?中的代碼,容易理解。)
單獨使用?CreateDelegate?方法可能每次都需要嘗試第一個參數到底應該傳入些什么,于是我將其封裝成了泛型版本,增加易用性。
using System;using System.Linq;
using System.Reflection;
using System.Diagnostics.Contracts;
namespace Walterlv.Demo
{
public static class InstanceMethodBuilder<T, TReturnValue>
{
/// <summary>
????????///?調用時就像?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>
????????///?調用時就像?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>));
}
}
}
泛型的多參數版本可以使用泛型類型生成器生成,我在?生成代碼,從?<T>?到?<T1,?T2,?Tn>?——?自動生成多個類型的泛型?-?呂毅?一文中寫了一個泛型生成器,可以稍加修改以便適應這種泛型類。
原文地址:https://blog.walterlv.com/post/create-delegate-to-improve-reflection-performance.html?
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的.NET Core/Framework 创建委托以大幅度提高反射调用的性能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#并行编程(6):线程同步面面观
- 下一篇: 中间件是什么?在.NET Core中的工