3 .Net 7內容匯總--反射優化

反射這玩意 , 一直以來都是慢的代名詞 。一說XXX系統大量的反射 , 好多人第一印象就是會慢 。
但是呢 , 我們又不得不使用反射來做一些事情,畢竟這玩意可以說啥都能干了對吧 。

It’s immensely powerful, providing the ability to query all of the metadata for code in your process and for arbitrary assemblies you might encounter, to invoke arbitrary functionality dynamically, and even to emit dynamically-generated IL at run-time.
當然.Net也提供了一些性能更高的方法 。
比如SG,這玩意是性能最好的方案,它在編譯的時候生成代碼 , 運行的時候一點反射沒有,同時也完美支持Native AOT 。但是呢,它還不是真正的動態生成,只能說是開發時動態 。所以更適合一些框架程序使用來提高執行效率 。
還有比如Emit,這玩意是動態編織IL代碼的,效率也比反射要快 。但是呢,寫起來極度復雜,10個人有8個都撓頭 。
所以,.Net 7里反射還是非常重要的一部分 , 也針對它做了一些比較牛逼的優化 。
  1. 我們知道,給MethodBase使用CreateDelegate<T>來創建一個委托,然后調用這個委托是最佳方法 。但是呢,我們編譯的時候經常是不知道這個方法簽名的,也就是沒法生成這個委托 。部分庫已經使用Emit來生成代碼提高速度了 。但是我們普通用戶顯然區寫一堆Emit是不現實的 。.Net 7優化后,會把我們的反射代碼優化為DynamicMethod形式的委托,然后調用 。
    我們來看一下數據
private MethodInfo _method;[GlobalSetup]public void Setup() => _method = typeof(Program).GetMethod("MyMethod", BindingFlags.NonPublic | BindingFlags.Static);[Benchmark]public void MethodInfoInvoke() => _method.Invoke(null, null);private static void MyMethod() { }Method
Runtime
Mean
Ratio
MethodInfoInvoke
.NET 6.0
43.846 ns
1.00
MethodInfoInvoke
.NET 7.0
8.078 ns
0.18
我們可以看到,這玩意速度提升了好幾倍 。
反射還有一個用處就是對類型、方法、屬性等等這些東西進行獲取 。一些其他的改進也會影響到這一部分 。比如.Net最近一直在做的把原生類型轉換為托管類型的工作,就產生了這么一個東西 。
[Benchmark]public Type GetUnderlyingType() => Enum.GetUnderlyingType(typeof(DayOfWeek));Method
Runtime
Mean
Ratio
GetUnderlyingType
.NET 6.0
27.413 ns
1.00
GetUnderlyingType
.NET 7.0
5.115 ns
0.19
是的,原生類型轉換為托管類型,不但沒有拖慢反射 , 反而讓它快了好幾倍 。
同樣的例子 , 有大量的AssemblyName的內容從原生轉向了CoreLib,所以Activator.CreateInstance也跟著變快了 。
private readonly string _assemblyName = typeof(MyClass).Assembly.FullName;private readonly string _typeName = typeof(MyClass).FullName;public class MyClass { }[Benchmark]public object CreateInstance() => Activator.CreateInstance(_assemblyName, _typeName);Method
Runtime
Mean
Ratio
CreateInstance
.NET 6.0
3.827 us
1.00
CreateInstance
.NET 7.0
2.276 us
0.60
這玩意雖然沒有那么夸張,但是提升可以說也是不小了 。
RuntimeType.CreateInstanceImpl現在使用Type.EmptyTypes代替了new Type[0],所以節省了一部分開銷 。
[Benchmark]public void CreateInstance() => Activator.CreateInstance(typeof(MyClass), BindingFlags.NonPublic | BindingFlags.Instance, null, Array.Empty<object>(), null);internal class MyClass{internal MyClass() { }}Method
Runtime
Mean
Ratio
Allocated
Alloc Ratio
CreateInstance
.NET 6.0
167.8 ns
1.00
320 B
1.00
CreateInstance
.NET 7.0
143.4 ns
0.85
200 B
0.62
我們再回到AssemblyName來,AssemblyName里把AssemblyName.FullName的實現由StringBuilder改為了ArrayPool<char> , 所以:
private AssemblyName[] _names = AppDomain.CurrentDomain.GetAssemblies().Select(a => new AssemblyName(a.FullName)).ToArray();[Benchmark]public int Names(){int sum = 0;foreach (AssemblyName name in _names){sum += name.FullName.Length;}return sum;}Method
Runtime
Mean
Ratio
Allocated
Alloc Ratio
Names
.NET 6.0
3.423 us
1.00
9.14 KB

推薦閱讀