.Net8通過各種騷操,把性能提升到了前所未有的高度。超越以往任何版本,也涵蓋了后續版本,比如.NET9或許可能沒有如此大的性能優化了。本篇來看下它其中的一個優化:類型轉換的優化效果。
通過類型檢查的優化,優化掉某些情況下類型轉換的時候JIT類型檢查的函數。下面的代碼是類型檢查的典型應用。
[HideColumns("Error", "StdDev", "Median", "RatioSD")][DisassemblyDiagnoser(maxDepth: 0)]public class Tests{ private readonly string[] _strings = new string[1]; [Benchmark] public string Get1() => _strings[0]; [Benchmark] public string Get2() => Volatile.Read(ref _strings[0]);}public partial class Program{ static void Main(string[] args) { BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args); }}
我們看到_strings是個私有數組,Get1函數中獲取_strings數組的第一個值。所以它是直接用ldelem.ref IL執行即可
ldelem.ref
但是Get2里面對數組元素進行了引用,所以Roslyn的指令是:
ldelema [System.Runtime]System.String
如果ref類型的變量,被賦值為不同于這個變量的類型則會違反類型安全性。通常情況下ldelema需要進行類型檢查,也就是用JIT輔助函數CORINFO_HELP_LDELEMA_REF來進行檢查,以確保不會違反類型安全性。
這個安全性的檢查會極大損耗性能,.NET8的JIT進行了一個優化,思路是如果是sealed關鍵字標記的類型,就不會進行安全性檢查,這樣就會提高性能。為什么sealed不會呢?
這其實是利用了它的一個特性,就是不會被繼承。不會被繼承,就不會被子類的類型所困擾,只有string一個類型,自然不會用以進行類型檢查了。
這是第一點優化,下面看下。
優化了類型安全檢查,縮短了編譯時間,提高了性能。來看下.Net7和.NET8的生成Get2函數的的不同點
.Net7:
Tests.Get2() sub rsp,28 mov rcx,[rcx+8] xor edx,edx mov r8,offset MT_System.String call CORINFO_HELP_LDELEMA_REF mov rax,[rax] add rsp,28 ret; Total bytes of code 33
.Net7它這里有一個CORINFO_HELP_LDELEMA_REF進行安全性檢查。
.Net8:
; Tests.Get2() sub rsp,28 mov rax,[rcx+8] cmp dword ptr [rax+8],0 jbe short M00_L00 mov rax,[rax+10] add rsp,28 retM00_L00: call CORINFO_HELP_RNGCHKFAIL int 3; Total bytes of code 29
.Net8里它沒有了CORINFO_HELP_LDELEMA_REF
因為string類型是sealed,它的原型如下:
public sealed class String : IEnumerable<char>, IEnumerable, ICloneable, IComparable, IComparable<String?>, IConvertible, IEquatable<String?>{ //這里代碼省略}
JIT會判斷類型是否是sealed標志,如果是則不進行安全性檢查優化。
雖然.Net8去掉了CORINFO_HELP_LDELEMA_REF,
但是多了范圍的檢查CORINFO_HELP_RNGCHKFAIL,那它這個性能如何呢?
我們來測試下:
dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
結果是:
Method | Runtime | Mean | Ratio | Code Size |
Get2 | .NET 7.0 | 1.0537 ns | 1.00 | 33 B |
Get2 | .NET 8.0 | 0.2423 ns | 0.23 | 29 B |
我們看到同樣代碼,.Net8里面比.Net7的性能提升了5倍之多。
承接上面,上面sealed去掉了類型檢查。
然后在類型轉換的時候,一般的類型轉換JIT使用的是CastHelpers.ChkCastAny來進行。
但是.Net8里面內聯了一個方法
用以縮短CastHelpers.ChkCastAny的編譯時間,提高編譯的時間和程序的性能。
using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;using System.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);[HideColumns("Error", "StdDev", "Median", "RatioSD")]public class Tests{ private readonly object _o = "hello"; [Benchmark] public string GetString() => Cast<string>(_o); [MethodImpl(MethodImplOptions.NoInlining)] public T Cast<T>(object o) => (T)o;}
同樣的
dotnet run -c Release -f net7.0 --filter "*" --runtimes net7.0 net8.0
結果如下:
Method | Runtime | Mean | Ratio |
GetString | .NET 7.0 | 3.018 ns | 1.00 |
GetString | .NET 8.0 | 1.198 ns | 0.40 |
.Net8是三倍于.Net7的運行速度。去掉類型檢查+類型轉換的內聯,大幅度的提升效率,可見.Net8的性能優化確實不容小覷。
參考如下:
https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/
最后推薦下個人的CLR/JIT交流圈,里面有多篇個人編寫的高質量的原創欄目和文章。學習心得,項目經驗等。帶你進入.Net核心技術階層,脫離curd工程師范疇。
本文鏈接:http://www.www897cc.com/showinfo-26-17278-0.html.Net8頂級性能優化:類型轉換
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Go 與數據可視化:使用 Gonum 和 Plot 庫探索數據之美
下一篇: 攜程后臺低代碼平臺的探究與實踐