原文 | Stephen Toub
翻译 | 郑子铭
一年前,我发布了.NET 6 中的性能改进,紧接着是.NET 5、.NET Core 3.0、.NET Core 2.1和.NET Core 2.0的类似帖子。我喜欢写这些帖子,也喜欢阅读开发人员对它们的回复。去年的一条评论特别引起了我的共鸣。评论者引用了虎胆龙威的电影名言,“'当亚历山大看到他的领域的广度时,他为没有更多的世界可以征服而哭泣'”,并质疑 .NET 的性能改进是否相似。水井干涸了吗?是否没有更多的“[性能]世界可以征服”?我有点头晕地说,即使 .NET 6 有多快,.NET 7 明确地强调了可以做的和已经做的更多。
与以前版本的 .NET 一样,性能是遍及整个堆栈的关键焦点,无论是明确为性能创建的功能,还是在设计和实现时仍牢记性能的非性能相关功能。现在 .NET 7 候选版本即将发布,现在是讨论其中大部分内容的好时机。在过去的一年中,每次我审查可能对性能产生积极影响的 PR 时,我都会将该链接复制到我维护的期刊中,以便撰写这篇文章。几周前,当我坐下来写这篇文章时,我看到了一份包含近 1000 个影响性能的 PR 的列表(在发布的 7000 多个 PR 中),我很高兴与您分享其中的近500个。
在我们深入探讨之前先想一想。在过去的几年里,我收到了一些奇怪的负面反馈,关于我一些以性能为中心的文章的长度,虽然我不同意这些批评,但我尊重他们的意见。因此,今年,将此视为“选择你自己的探险”。如果您来这里只是为了寻找一个超短的探险,一个提供顶级摘要和核心信息的探险,以节省您在这里的时间,我很乐意为您效劳:
TL;DR:.NET 7 很快。真的很快。上千个影响性能的 PR 进入了这个版本的运行时和核心库,更不用说 ASP.NET Core 和 Windows Forms 以及 Entity Framework 和其他方面的所有改进。它是有史以来最快的 .NET。如果你的经理问你为什么你的项目应该升级到 .NET 7,你可以说“除了版本中的所有新功能之外,.NET 7 超级快。”
或者,如果您更喜欢稍微长一点的探险,其中充满了有趣的以性能为中心的数据块,请考虑浏览一下帖子,寻找小代码片段和相应的表格,这些表格显示了大量可衡量的性能改进。到那时,你也可能会昂首阔步地走开并表达我的谢意。
两条提到的路径都实现了我花时间写这些帖子的主要目标之一,以突出下一个版本的伟大之处并鼓励大家尝试一下。但是,我对这些帖子也有其他目标。我希望每个感兴趣的人在看完这篇文章后,都能对.NET是如何实现的,为什么会做出各种决定,评估了各种权衡,采用了哪些技术,考虑了哪些算法,以及利用了哪些有价值的工具和方法来使.NET比以前更快。我希望开发人员从我们自己的学习中学习,并找到将这些新发现的知识应用到他们自己的代码库中的方法,从而进一步提高生态系统中代码的整体性能。我希望开发人员多做一些工作,考虑在他们下次处理棘手问题时寻求探查器,考虑查看他们正在使用的组件的源代码以更好地理解如何使用它,并考虑重新审视以前的假设和决策以确定它们是否仍然准确和适当。我希望开发人员对提交 PR 以改进 .NET 的前景感到兴奋,不仅是为了他们自己,也是为了全球使用 .NET 的每个开发人员。如果其中任何一个听起来很有趣,那么我鼓励您选择最后一个探险:准备一瓶您最喜欢的热饮,放松一下,尽情享受吧。
哦,请不要把这个打印到纸上。"打印成PDF "告诉我这将需要三分之一的卷轴。如果你想要一个格式很好的PDF,这里有一个可以下载。
目录
- Setup
- JIT
- GC
- Native AOT
- Mono
- Reflection
- Interop
- Threading
- Primitive Types and Numerics
- Arrays, Strings, and Spans
- Regex
- Collections
- LINQ
- File I/O
- Compression
- Networking
- JSON
- XML
- Cryptography
- Diagnostics
- Exceptions
- Registry
- Analyzers
- What’s Next?
Setup
这篇文章中的微基准测试使用 benchmarkdotnet。为了让您更轻松地进行自己的验证,我为我使用的基准设置了一个非常简单的设置。创建一个新的 C# 项目:
dotnet new console -o benchmarks cd benchmarks
您的新基准目录将包含一个 benchmarks.csproj 文件和一个 Program.cs 文件。将 benchmarks.csproj 的内容替换为:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFrameworks>net7.0;net6.0</TargetFrameworks> <LangVersion>Preview</LangVersion> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <ServerGarbageCollection>true</ServerGarbageCollection> </PropertyGroup> <ItemGroup> <PackageReference Include="benchmarkdotnet" Version="0.13.2" /> </ItemGroup> </Project>
以及 Program.cs 的内容:
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using Microsoft.Win32; using System; using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.IO.MemoryMappedFiles; using System.IO.Pipes; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Security; using System.Net.Sockets; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml; [MemoryDiagnoser(displayGenColumns: false)] [DisassemblyDiagnoser] [HideColumns("Error", "StdDev", "Median", "RatioSD")] public partial class Program { static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); // ... copy [Benchmark]s here }
对于本文中包含的每个基准测试,您只需将代码复制并粘贴到该测试类中,然后运行基准测试。例如,要运行基准比较 .NET 6 和 .NET 7 上的性能,请执行以下操作:
dotnet run -c Release -f net6.0 --filter '**' --runtimes net6.0 net7.0
此命令表示“在针对 .NET 6 表面区域的发布配置中构建基准测试,然后在 .NET 6 和 .NET 7 上运行所有基准测试。”或者只在 .NET 7 上运行:
dotnet run -c Release -f net7.0 --filter '**' --runtimes net7.0
它针对 .NET 7 表面区域构建,然后只针对 .NET 7 运行一次。您可以在任何 Windows、Linux 或 macOS 上执行此操作。除非另有说明(例如,改进是针对 Unix 的,我在 Linux 上运行基准测试),否则我分享的结果是在 Windows 11 64 位上记录的,但不是特定于 Windows 的,并且应该在另一个上显示类似的相对差异操作系统也是如此。
第一个 .NET 7 候选版本即将发布。这篇文章中的所有测量值都是通过最近的 .NET 7 RC1 每日构建收集的。
另外,我的标准警告:这些是微观的基准测试。预计不同的硬件、不同版本的操作系统以及目前的风向都会影响相关的数字。你的里程数可能会有所不同。
JIT
我想通过讨论一些本身并不是性能改进的东西来开始对实时 (Just-In-Time) (JIT) 编译器中性能改进的讨论。在微调较低级别的性能敏感代码时,能够准确理解 JIT 生成的汇编代码至关重要。有多种方法可以获取该汇编代码。在线工具 sharplab.io 对此非常有用(感谢@ashmind 提供此工具);然而,它目前只针对一个版本,所以在我写这篇文章时,我只能看到 .NET 6 的输出,这使得它很难用于 A/B 比较。 godbolt.org 对此也很有价值,在@hez2010 的 compiler-explorer/compiler-explorer#3168 中添加了 C# 支持,具有类似的限制。最灵活的解决方案涉及在本地获取该汇编代码,因为它可以将您想要的任何版本或本地构建与您需要的任何配置和开关集进行比较。
一种常见的方法是使用 benchmarkdotnet 中的 [DisassemblyDiagnoser]。只需将 [DisassemblyDiagnoser] 属性添加到您的测试类上:benchmarkdotnet 将找到为您的测试生成的汇编代码以及它们调用的一些深度函数,并以人类可读的形式转储找到的汇编代码。例如,如果我运行这个测试:
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; [DisassemblyDiagnoser] public partial class Program { static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); private int _a = 42, _b = 84; [Benchmark] public int Min() => Math.Min(_a, _b); }
和
dotnet run -c Release -f net7.0 --filter '**'
除了执行所有正常的测试执行和计时之外,benchmarkdotnet 还输出一个包含以下内容的 Program-asm.md 文件:
; Program.Min() mov eax,[rcx+8] mov edx,[rcx+0C] cmp eax,edx jg short M00_L01 mov edx,eax M00_L00: mov eax,edx ret M00_L01: jmp short M00_L00 ; Total bytes of code 17
很简约。这种支持最近在 dotnet/benchmarkdotnet#2072 中得到了进一步改进,它允许将命令行上的过滤器列表传递给 benchmarkdotnet,以准确地告诉它应该转储哪些方法的汇编代码。
如果你能得到.NET运行时的 "调试 "或 "检查 "版本("检查 "是指已启用优化但仍包括断言的版本),特别是clrjit.dll,另一个有价值的方法是设置一个环境变量,使JIT本身吐出它发出的所有汇编代码的人类可读描述。这可以用于任何类型的应用程序,因为它是JIT本身的一部分,而不是任何特定工具或其他环境的一部分,它支持显示JIT每次生成代码时产生的代码(例如,如果它首先编译一个没有优化的方法,然后用优化重新编译),总的来说,它是最准确的汇编代码图片,因为它 "直接来自马嘴",如它。当然,最大的缺点是它需要一个非发布版的运行时,这通常意味着你需要从dotnet/runtime repo中的源码自己构建它。
……直到 .NET 7,也就是说。从 dotnet/runtime#73365 开始,此程序集转储支持现在也可在发布版本中使用,这意味着它只是 .NET 7 的一部分,您不需要任何特殊的东西即可使用它。要看到这一点,请尝试创建一个简单的“hello world”应用程序,例如:
using System; class Program { public static void Main() => Console.WriteLine("Hello, world!"); }
并构建它(例如 dotnet build -c Release)。然后,将 DOTNET_JitDisasm 环境变量设置为我们关心的方法的名称,在本例中为“Main”(允许的确切语法更为宽松,并允许使用通配符、可选的命名空间和类名等)。当我使用 PowerShell 时,这意味着:
$env:DOTNET_JitDisasm="Main"
然后运行应用程序。您应该会在控制台看到类似这样的代码输出:
; Assembly listing for method Program:Main() ; Emitting BLENDED_CODE for X64 CPU with AVX - Windows ; Tier-0 compilation ; MinOpts code ; rbp based frame ; partially interruptible G_M000_IG01: ;; offset=0000H 55 push rbp 4883EC20 sub rsp, 32 488D6C2420 lea rbp, [rsp+20H] G_M000_IG02: ;; offset=000AH 48B9D820400A8E010000 mov rcx, 0x18E0A4020D8 488B09 mov rcx, gword ptr [rcx] FF1583B31000 call [Console:WriteLine(String)] 90 nop G_M000_IG03: ;; offset=001EH 4883C420 add rsp, 32 5D pop rbp C3 ret ; Total bytes of code 36 Hello, world!
这对性能分析和调整有不可估量的帮助,甚至对于像 "我的函数是否被内联 "或 "我期望被优化掉的这段代码是否真的被优化掉了 "这样简单的问题。在这篇文章的其余部分,我将包括由这两种机制之一产生的汇编片段,以帮助说明概念。
请注意,确定指定什么名称作为 DOTNET_JitDisasm 的值有时会有点混乱,尤其是当您关心的方法是 C# 编译器命名或名称混淆的方法时(因为 JIT 只看到 IL 和元数据,而不是原始的 C#),例如具有顶级语句的程序的入口点方法的名称、本地函数的名称等。为了帮助解决这个问题并提供 JIT 正在执行的工作的真正有价值的顶级视图,.NET 7还支持新的 DOTNET_JitDisasmSummary 环境变量(在 dotnet/runtime#74090 中引入)。将其设置为“1”,这将导致 JIT 每次编译一个方法时都会发出一行,包括该方法的名称,该名称可使用 DOTNET_JitDisasm 进行复制/粘贴。但是,此功能本身很有用,因为它可以快速突出显示正在编译的内容、时间和设置。例如,如果我设置环境变量然后运行“hello, world”控制台应用程序,我会得到以下输出:
1: JIT compiled CastHelpers:StelemRef(Array,long,Object) [Tier1, IL size=88, code size=93] 2: JIT compiled CastHelpers:LdelemaRef(Array,long,long):byref [Tier1, IL size=44, code size=44] 3: JIT compiled SpanHelpers:IndexOfNullCharacter(byref):int [Tier1, IL size=792, code size=388] 4: JIT compiled Program:Main() [Tier0, IL size=11, code size=36] 5: JIT compiled ASCIIUtility:NarrowUtf16ToAscii(long,long,long):long [Tier0, IL size=490, code size=1187] Hello, world!
我们可以看到,对于“hello, world”,只有 5 个方法实际得到了 JIT 编译。当然还有更多的方法作为简单的“hello, world”的一部分执行,但几乎所有方法都有预编译的本机代码,作为核心库的“准备运行 (Ready To Run)”(R2R) 映像的一部分可用。上面列表中的前三个(StelemRef、LdelemaRef 和 IndexOfNullCharacter)没有,因为它们通过使用 [MethodImpl(MethodImplOptions.AggressiveOptimization)] 属性明确选择退出 R2R(尽管有名称,但这个属性几乎不应该是使用,并且仅出于非常特定的原因在核心库中的几个非常特定的地方使用)。然后是我们的 Main 方法。最后是 NarrowUtf16ToAscii 方法,它也没有 R2R 代码,因为使用了可变宽度的 Vector
1: JIT compiled CastHelpers:StelemRef(Array,long,Object) [Tier1, IL size=88, code size=93] 2: JIT compiled CastHelpers:LdelemaRef(Array,long,long):byref [Tier1, IL size=44, code size=44] 3: JIT compiled AppContext:Setup(long,long,int) [Tier0, IL size=68, code size=275] 4: JIT compiled Dictionary`2:.ctor(int):this [Tier0, IL size=9, code size=40] 5: JIT compiled Dictionary`2:.ctor(int,IEqualityComparer`1):this [Tier0, IL size=102, code size=444] 6: JIT compiled Object:.ctor():this [Tier0, IL size=1, code size=10] 7: JIT compiled Dictionary`2:Initialize(int):int:this [Tier0, IL size=56, code size=231] 8: JIT compiled HashHelpers:GetPrime(int):int [Tier0, IL size=83, code size=379] 9: JIT compiled HashHelpers:.cctor() [Tier0, IL size=24, code size=102] 10: JIT compiled HashHelpers:GetFastModMultiplier(int):long [Tier0, IL size=9, code size=37] 11: JIT compiled Type:GetTypeFromHandle(RuntimeTypeHandle):Type [Tier0, IL size=8, code size=14] 12: JIT compiled Type:op_Equality(Type,Type):bool [Tier0, IL size=38, code size=143] 13: JIT compiled NonRandomizedStringEqualityComparer:GetStringComparer(Object):IEqualityComparer`1 [Tier0, IL size=39, code size=170] 14: JIT compiled NonRandomizedStringEqualityComparer:.cctor() [Tier0, IL size=46, code size=232] 15: JIT compiled EqualityComparer`1:get_Default():EqualityComparer`1 [Tier0, IL size=6, code size=36] 16: JIT compiled EqualityComparer`1:.cctor() [Tier0, IL size=26, code size=125] 17: JIT compiled ComparerHelpers:CreateDefaultEqualityComparer(Type):Object [Tier0, IL size=235, code size=949] 18: JIT compiled CastHelpers:ChkCastClass(long,Object):Object [Tier0, IL size=22, code size=72] 19: JIT compiled RuntimeHelpers:GetMethodTable(Object):long [Tier0, IL size=11, code size=33] 20: JIT compiled CastHelpers:IsInstanceOfClass(long,Object):Object [Tier0, IL size=97, code size=257] 21: JIT compiled GenericEqualityComparer`1:.ctor():this [Tier0, IL size=7, code size=31] 22: JIT compiled EqualityComparer`1:.ctor():this [Tier0, IL size=7, code size=31] 23: JIT compiled CastHelpers:ChkCastClassSpecial(long,Object):Object [Tier0, IL size=87, code size=246] 24: JIT compiled OrdinalComparer:.ctor(IEqualityComparer`1):this [Tier0, IL size=8, code size=39] 25: JIT compiled NonRandomizedStringEqualityComparer:.ctor(IEqualityComparer`1):this [Tier0, IL size=14, code size=52] 26: JIT compiled StringComparer:get_Ordinal():StringComparer [Tier0, IL size=6, code size=49] 27: JIT compiled OrdinalCaseSensitiveComparer:.cctor() [Tier0, IL size=11, code size=71] 28: JIT compiled OrdinalCaseSensitiveComparer:.ctor():this [Tier0, IL size=8, code size=33] 29: JIT compiled OrdinalComparer:.ctor(bool):this [Tier0, IL size=14, code size=43] 30: JIT compiled StringComparer:.ctor():this [Tier0, IL size=7, code size=31] 31: JIT compiled StringComparer:get_OrdinalIgnoreCase():StringComparer [Tier0, IL size=6, code size=49] 32: JIT compiled OrdinalIgnoreCaseComparer:.cctor() [Tier0, IL size=11, code size=71] 33: JIT compiled OrdinalIgnoreCaseComparer:.ctor():this [Tier0, IL size=8, code size=36] 34: JIT compiled OrdinalIgnoreCaseComparer:.ctor(IEqualityComparer`1):this [Tier0, IL size=8, code size=39] 35: JIT compiled CastHelpers:ChkCastAny(long,Object):Object [Tier0, IL size=38, code size=115] 36: JIT compiled CastHelpers:TryGet(long,long):int [Tier0, IL size=129, code size=308] 37: JIT compiled CastHelpers:TableData(ref):byref [Tier0, IL size=7, code size=31] 38: JIT compiled MemoryMarshal:GetArrayDataReference(ref):byref [Tier0, IL size=7, code size=24] 39: JIT compiled CastHelpers:KeyToBucket(byref,long,long):int [Tier0, IL size=38, code size=87] 40: JIT compiled CastHelpers:HashShift(byref):int [Tier0, IL size=3, code size=16] 41: JIT compiled BitOperations:RotateLeft(long,int):long [Tier0, IL size=17, code size=23] 42: JIT compiled CastHelpers:Element(byref,int):byref [Tier0, IL size=15, code size=33] 43: JIT compiled Volatile:Read(byref):int [Tier0, IL size=6, code size=16] 44: JIT compiled String:Ctor(long):String [Tier0, IL size=57, code size=155] 45: JIT compiled String:wcslen(long):int [Tier0, IL size=7, code size=31] 46: JIT compiled SpanHelpers:IndexOfNullCharacter(byref):int [Tier1, IL size=792, code size=388] 47: JIT compiled String:get_Length():int:this [Tier0, IL size=7, code size=17] 48: JIT compiled Buffer:Memmove(byref,byref,long) [Tier0, IL size=59, code size=102] 49: JIT compiled RuntimeHelpers:IsReferenceOrContainsReferences():bool [Tier0, IL size=2, code size=8] 50: JIT compiled Buffer:Memmove(byref,byref,long) [Tier0, IL size=480, code size=678] 51: JIT compiled Dictionary`2:Add(__Canon,__Canon):this [Tier0, IL size=11, code size=55] 52: JIT compiled Dictionary`2:TryInsert(__Canon,__Canon,ubyte):bool:this [Tier0, IL size=675, code size=2467] 53: JIT compiled OrdinalComparer:GetHashCode(String):int:this [Tier0, IL size=7, code size=37] 54: JIT compiled String:GetNonRandomizedHashCode():int:this [Tier0, IL size=110, code size=290] 55: JIT compiled BitOperations:RotateLeft(int,int):int [Tier0, IL size=17, code size=20] 56: JIT compiled Dictionary`2:GetBucket(int):byref:this [Tier0, IL size=29, code size=90] 57: JIT compiled HashHelpers:FastMod(int,int,long):int [Tier0, IL size=20, code size=70] 58: JIT compiled Type:get_IsValueType():bool:this [Tier0, IL size=7, code size=39] 59: JIT compiled RuntimeType:IsValueTypeImpl():bool:this [Tier0, IL size=54, code size=158] 60: JIT compiled RuntimeType:GetNativeTypeHandle():TypeHandle:this [Tier0, IL size=12, code size=48] 61: JIT compiled TypeHandle:.ctor(long):this [Tier0, IL size=8, code size=25] 62: JIT compiled TypeHandle:get_IsTypeDesc():bool:this [Tier0, IL size=14, code size=38] 63: JIT compiled TypeHandle:AsMethodTable():long:this [Tier0, IL size=7, code size=17] 64: JIT compiled MethodTable:get_IsValueType():bool:this [Tier0, IL size=20, code size=32] 65: JIT compiled GC:KeepAlive(Object) [Tier0, IL size=1, code size=10] 66: JIT compiled Buffer:_Memmove(byref,byref,long) [Tier0, IL size=25, code size=279] 67: JIT compiled Environment:InitializeCommandLineArgs(long,int,long):ref [Tier0, IL size=75, code size=332] 68: JIT compiled Environment:.cctor() [Tier0, IL size=11, code size=163] 69: JIT compiled StartupHookProvider:ProcessStartupHooks() [Tier-0 switched to FullOpts, IL size=365, code size=1053] 70: JIT compiled StartupHookProvider:get_IsSupported():bool [Tier0, IL size=18, code size=60] 71: JIT compiled AppContext:TryGetSwitch(String,byref):bool [Tier0, IL size=97, code size=322] 72: JIT compiled ArgumentException:ThrowIfNullOrEmpty(String,String) [Tier0, IL size=16, code size=53] 73: JIT compiled String:IsNullOrEmpty(String):bool [Tier0, IL size=15, code size=58] 74: JIT compiled AppContext:GetData(String):Object [Tier0, IL size=64, code size=205] 75: JIT compiled ArgumentNullException:ThrowIfNull(Object,String) [Tier0, IL size=10, code size=42] 76: JIT compiled Monitor:Enter(Object,byref) [Tier0, IL size=17, code size=55] 77: JIT compiled Dictionary`2:TryGetValue(__Canon,byref):bool:this [Tier0, IL size=39, code size=97] 78: JIT compiled Dictionary`2:FindValue(__Canon):byref:this [Tier0, IL size=391, code size=1466] 79: JIT compiled EventSource:.cctor() [Tier0, IL size=34, code size=80] 80: JIT compiled EventSource:InitializeIsSupported():bool [Tier0, IL size=18, code size=60] 81: JIT compiled RuntimeEventSource:.ctor():this [Tier0, IL size=55, code size=184] 82: JIT compiled Guid:.ctor(int,short,short,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte):this [Tier0, IL size=86, code size=132] 83: JIT compiled EventSource:.ctor(Guid,String):this [Tier0, IL size=11, code size=90] 84: JIT compiled EventSource:.ctor(Guid,String,int,ref):this [Tier0, IL size=58, code size=187] 85: JIT compiled EventSource:get_IsSupported():bool [Tier0, IL size=6, code size=11] 86: JIT compiled TraceLoggingEventHandleTable:.ctor():this [Tier0, IL size=20, code size=67] 87: JIT compiled EventSource:ValidateSettings(int):int [Tier0, IL size=37, code size=147] 88: JIT compiled EventSource:Initialize(Guid,String,ref):this [Tier0, IL size=418, code size=1584] 89: JIT compiled Guid:op_Equality(Guid,Guid):bool [Tier0, IL size=10, code size=39] 90: JIT compiled Guid:EqualsCore(byref,byref):bool [Tier0, IL size=132, code size=171] 91: JIT compiled ActivityTracker:get_Instance():ActivityTracker [Tier0, IL size=6, code size=49] 92: JIT compiled ActivityTracker:.cctor() [Tier0, IL size=11, code size=71] 93: JIT compiled ActivityTracker:.ctor():this [Tier0, IL size=7, code size=31] 94: JIT compiled RuntimeEventSource:get_ProviderMetadata():ReadOnlySpan`1:this [Tier0, IL size=13, code size=91] 95: JIT compiled ReadOnlySpan`1:.ctor(long,int):this [Tier0, IL size=51, code size=115] 96: JIT compiled RuntimeHelpers:IsReferenceOrContainsReferences():bool [Tier0, IL size=2, code size=8] 97: JIT compiled ReadOnlySpan`1:get_Length():int:this [Tier0, IL size=7, code size=17] 98: JIT compiled OverrideEventProvider:.ctor(EventSource,int):this [Tier0, IL size=22, code size=68] 99: JIT compiled EventProvider:.ctor(int):this [Tier0, IL size=46, code size=194] 100: JIT compiled EtwEventProvider:.ctor():this [Tier0, IL size=7, code size=31] 101: JIT compiled EventProvider:Register(EventSource):this [Tier0, IL size=48, code size=186] 102: JIT compiled MulticastDelegate:CtorClosed(Object,long):this [Tier0, IL size=23, code size=70] 103: JIT compiled EventProvider:EventRegister(EventSource,EtwEnableCallback):int:this [Tier0, IL size=53, code size=154] 104: JIT compiled EventSource:get_Name():String:this [Tier0, IL size=7, code size=18] 105: JIT compiled EventSource:get_Guid():Guid:this [Tier0, IL size=7, code size=41] 106: JIT compiled EtwEventProvider:System.Diagnostics.Tracing.IEventProvider.EventRegister(EventSource,EtwEnableCallback,long,byref):int:this [Tier0, IL size=19, code size=71] 107: JIT compiled Advapi32:EventRegister(byref,EtwEnableCallback,long,byref):int [Tier0, IL size=53, code size=374] 108: JIT compiled Marshal:GetFunctionPointerForDelegate(__Canon):long [Tier0, IL size=17, code size=54] 109: JIT compiled Marshal:GetFunctionPointerForDelegate(Delegate):long [Tier0, IL size=18, code size=53] 110: JIT compiled EventPipeEventProvider:.ctor():this [Tier0, IL size=18, code size=41] 111: JIT compiled EventListener:get_EventListenersLock():Object [Tier0, IL size=41, code size=157] 112: JIT compiled List`1:.ctor(int):this [Tier0, IL size=47, code size=275] 113: JIT compiled Interlocked:CompareExchange(byref,__Canon,__Canon):__Canon [Tier0, IL size=9, code size=50] 114: JIT compiled NativeRuntimeEventSource:.cctor() [Tier0, IL size=11, code size=71] 115: JIT compiled NativeRuntimeEventSource:.ctor():this [Tier0, IL size=63, code size=184] 116: JIT compiled Guid:.ctor(int,ushort,ushort,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte,ubyte):this [Tier0, IL size=88, code size=132] 117: JIT compiled NativeRuntimeEventSource:get_ProviderMetadata():ReadOnlySpan`1:this [Tier0, IL size=13, code size=91] 118: JIT compiled EventPipeEventProvider:System.Diagnostics.Tracing.IEventProvider.EventRegister(EventSource,EtwEnableCallback,long,byref):int:this [Tier0, IL size=44, code size=118] 119: JIT compiled EventPipeInternal:CreateProvider(String,EtwEnableCallback):long [Tier0, IL size=43, code size=320] 120: JIT compiled Utf16StringMarshaller:GetPinnableReference(String):byref [Tier0, IL size=13, code size=50] 121: JIT compiled String:GetPinnableReference():byref:this [Tier0, IL size=7, code size=24] 122: JIT compiled EventListener:AddEventSource(EventSource) [Tier0, IL size=175, code size=560] 123: JIT compiled List`1:get_Count():int:this [Tier0, IL size=7, code size=17] 124: JIT compiled WeakReference`1:.ctor(__Canon):this [Tier0, IL size=9, code size=42] 125: JIT compiled WeakReference`1:.ctor(__Canon,bool):this [Tier0, IL size=15, code size=60] 126: JIT compiled List`1:Add(__Canon):this [Tier0, IL size=60, code size=124] 127: JIT compiled String:op_Inequality(String,String):bool [Tier0, IL size=11, code size=46] 128: JIT compiled String:Equals(String,String):bool [Tier0, IL size=36, code size=114] 129: JIT compiled ReadOnlySpan`1:GetPinnableReference():byref:this [Tier0, IL size=23, code size=57] 130: JIT compiled EventProvider:SetInformation(int,long,int):int:this [Tier0, IL size=38, code size=131] 131: JIT compiled ILStubClass:IL_STUB_PInvoke(long,int,long,int):int [FullOpts, IL size=62, code size=170] 132: JIT compiled Program:Main() [Tier0, IL size=11, code size=36] 133: JIT compiled Console:WriteLine(String) [Tier0, IL size=12, code size=59] 134: JIT compiled Console:get_Out():TextWriter [Tier0, IL size=20, code size=113] 135: JIT compiled Console:.cctor() [Tier0, IL size=11, code size=71] 136: JIT compiled Volatile:Read(byref):__Canon [Tier0, IL size=6, code size=21] 137: JIT compiled Console:<get_Out>g__EnsureInitialized|26_0():TextWriter [Tier0, IL size=63, code size=209] 138: JIT compiled ConsolePal:OpenStandardOutput():Stream [Tier0, IL size=34, code size=130] 139: JIT compiled Console:get_OutputEncoding():Encoding [Tier0, IL size=72, code size=237] 140: JIT compiled ConsolePal:get_OutputEncoding():Encoding [Tier0, IL size=11, code size=200] 141: JIT compiled NativeLibrary:LoadLibraryCallbackStub(String,Assembly,bool,int):long [Tier0, IL size=63, code size=280] 142: JIT compiled EncodingHelper:GetSupportedConsoleEncoding(int):Encoding [Tier0, IL size=53, code size=186] 143: JIT compiled Encoding:GetEncoding(int):Encoding [Tier0, IL size=340, code size=1025] 144: JIT compiled EncodingProvider:GetEncodingFromProvider(int):Encoding [Tier0, IL size=51, code size=232] 145: JIT compiled Encoding:FilterDisallowedEncodings(Encoding):Encoding [Tier0, IL size=29, code size=84] 146: JIT compiled LocalAppContextSwitches:get_EnableUnsafeUTF7Encoding():bool [Tier0, IL size=16, code size=46] 147: JIT compiled LocalAppContextSwitches:GetCachedSwitchValue(String,byref):bool [Tier0, IL size=22, code size=76] 148: JIT compiled LocalAppContextSwitches:GetCachedSwitchValueInternal(String,byref):bool [Tier0, IL size=46, code size=168] 149: JIT compiled LocalAppContextSwitches:GetSwitchDefaultValue(String):bool [Tier0, IL size=32, code size=98] 150: JIT compiled String:op_Equality(String,String):bool [Tier0, IL size=8, code size=39] 151: JIT compiled Encoding:get_Default():Encoding [Tier0, IL size=6, code size=49] 152: JIT compiled Encoding:.cctor() [Tier0, IL size=12, code size=73] 153: JIT compiled UTF8EncodingSealed:.ctor(bool):this [Tier0, IL size=8, code size=40] 154: JIT compiled UTF8Encoding:.ctor(bool):this [Tier0, IL size=14, code size=43] 155: JIT compiled UTF8Encoding:.ctor():this [Tier0, IL size=12, code size=36] 156: JIT compiled Encoding:.ctor(int):this [Tier0, IL size=42, code size=152] 157: JIT compiled UTF8Encoding:SetDefaultFallbacks():this [Tier0, IL size=64, code size=212] 158: JIT compiled EncoderReplacementFallback:.ctor(String):this [Tier0, IL size=110, code size=360] 159: JIT compiled EncoderFallback:.ctor():this [Tier0, IL size=7, code size=31] 160: JIT compiled String:get_Chars(int):ushort:this [Tier0, IL size=29, code size=61] 161: JIT compiled Char:IsSurrogate(ushort):bool [Tier0, IL size=17, code size=43] 162: JIT compiled Char:IsBetween(ushort,ushort,ushort):bool [Tier0, IL size=12, code size=52] 163: JIT compiled DecoderReplacementFallback:.ctor(String):this [Tier0, IL size=110, code size=360] 164: JIT compiled DecoderFallback:.ctor():this [Tier0, IL size=7, code size=31] 165: JIT compiled Encoding:get_CodePage():int:this [Tier0, IL size=7, code size=17] 166: JIT compiled Encoding:get_UTF8():Encoding [Tier0, IL size=6, code size=49] 167: JIT compiled UTF8Encoding:.cctor() [Tier0, IL size=12, code size=76] 168: JIT compiled Volatile:Write(byref,__Canon) [Tier0, IL size=6, code size=32] 169: JIT compiled ConsolePal:GetStandardFile(int,int,bool):Stream [Tier0, IL size=50, code size=183] 170: JIT compiled ConsolePal:get_InvalidHandleValue():long [Tier0, IL size=7, code size=41] 171: JIT compiled IntPtr:.ctor(int):this [Tier0, IL size=9, code size=25] 172: JIT compiled ConsolePal:ConsoleHandleIsWritable(long):bool [Tier0, IL size=26, code size=68] 173: JIT compiled Kernel32:WriteFile(long,long,int,byref,long):int [Tier0, IL size=46, code size=294] 174: JIT compiled Marshal:SetLastSystemError(int) [Tier0, IL size=7, code size=40] 175: JIT compiled Marshal:GetLastSystemError():int [Tier0, IL size=6, code size=34] 176: JIT compiled WindowsConsoleStream:.ctor(long,int,bool):this [Tier0, IL size=37, code size=90] 177: JIT compiled ConsoleStream:.ctor(int):this [Tier0, IL size=31, code size=71] 178: JIT compiled Stream:.ctor():this [Tier0, IL size=7, code size=31] 179: JIT compiled MarshalByRefObject:.ctor():this [Tier0, IL size=7, code size=31] 180: JIT compiled Kernel32:GetFileType(long):int [Tier0, IL size=27, code size=217] 181: JIT compiled Console:CreateOutputWriter(Stream):TextWriter [Tier0, IL size=50, code size=230] 182: JIT compiled Stream:.cctor() [Tier0, IL size=11, code size=71] 183: JIT compiled NullStream:.ctor():this [Tier0, IL size=7, code size=31] 184: JIT compiled EncodingExtensions:RemovePreamble(Encoding):Encoding [Tier0, IL size=25, code size=118] 185: JIT compiled UTF8EncodingSealed:get_Preamble():ReadOnlySpan`1:this [Tier0, IL size=24, code size=99] 186: JIT compiled UTF8Encoding:get_PreambleSpan():ReadOnlySpan`1 [Tier0, IL size=12, code size=87] 187: JIT compiled ConsoleEncoding:.ctor(Encoding):this [Tier0, IL size=14, code size=52] 188: JIT compiled Encoding:.ctor():this [Tier0, IL size=8, code size=33] 189: JIT compiled Encoding:SetDefaultFallbacks():this [Tier0, IL size=23, code size=65] 190: JIT compiled EncoderFallback:get_ReplacementFallback():EncoderFallback [Tier0, IL size=6, code size=49] 191: JIT compiled EncoderReplacementFallback:.cctor() [Tier0, IL size=11, code size=71] 192: JIT compiled EncoderReplacementFallback:.ctor():this [Tier0, IL size=12, code size=44] 193: JIT compiled DecoderFallback:get_ReplacementFallback():DecoderFallback [Tier0, IL size=6, code size=49] 194: JIT compiled DecoderReplacementFallback:.cctor() [Tier0, IL size=11, code size=71] 195: JIT compiled DecoderReplacementFallback:.ctor():this [Tier0, IL size=12, code size=44] 196: JIT compiled StreamWriter:.ctor(Stream,Encoding,int,bool):this [Tier0, IL size=201, code size=564] 197: JIT compiled Task:get_CompletedTask():Task [Tier0, IL size=6, code size=49] 198: JIT compiled Task:.cctor() [Tier0, IL size=76, code size=316] 199: JIT compiled TaskFactory:.ctor():this [Tier0, IL size=7, code size=31] 200: JIT compiled Task`1:.ctor(bool,VoidTaskResult,int,CancellationToken):this [Tier0, IL size=21, code size=75] 201: JIT compiled Task:.ctor(bool,int,CancellationToken):this [Tier0, IL size=70, code size=181] 202: JIT compiled <>c:.cctor() [Tier0, IL size=11, code size=71] 203: JIT compiled <>c:.ctor():this [Tier0, IL size=7, code size=31] 204: JIT compiled TextWriter:.ctor(IFormatProvider):this [Tier0, IL size=36, code size=124] 205: JIT compiled TextWriter:.cctor() [Tier0, IL size=26, code size=108] 206: JIT compiled NullTextWriter:.ctor():this [Tier0, IL size=7, code size=31] 207: JIT compiled TextWriter:.ctor():this [Tier0, IL size=29, code size=103] 208: JIT compiled String:ToCharArray():ref:this [Tier0, IL size=52, code size=173] 209: JIT compiled MemoryMarshal:GetArrayDataReference(ref):byref [Tier0, IL size=7, code size=24] 210: JIT compiled ConsoleStream:get_CanWrite():bool:this [Tier0, IL size=7, code size=18] 211: JIT compiled ConsoleEncoding:GetEncoder():Encoder:this [Tier0, IL size=12, code size=57] 212: JIT compiled UTF8Encoding:GetEncoder():Encoder:this [Tier0, IL size=7, code size=63] 213: JIT compiled EncoderNLS:.ctor(Encoding):this [Tier0, IL size=37, code size=102] 214: JIT compiled Encoder:.ctor():this [Tier0, IL size=7, code size=31] 215: JIT compiled Encoding:get_EncoderFallback():EncoderFallback:this [Tier0, IL size=7, code size=18] 216: JIT compiled EncoderNLS:Reset():this [Tier0, IL size=24, code size=92] 217: JIT compiled ConsoleStream:get_CanSeek():bool:this [Tier0, IL size=2, code size=12] 218: JIT compiled StreamWriter:set_AutoFlush(bool):this [Tier0, IL size=25, code size=72] 219: JIT compiled StreamWriter:CheckAsyncTaskInProgress():this [Tier0, IL size=19, code size=47] 220: JIT compiled Task:get_IsCompleted():bool:this [Tier0, IL size=16, code size=40] 221: JIT compiled Task:IsCompletedMethod(int):bool [Tier0, IL size=11, code size=25] 222: JIT compiled StreamWriter:Flush(bool,bool):this [Tier0, IL size=272, code size=1127] 223: JIT compiled StreamWriter:ThrowIfDisposed():this [Tier0, IL size=15, code size=43] 224: JIT compiled Encoding:get_Preamble():ReadOnlySpan`1:this [Tier0, IL size=12, code size=70] 225: JIT compiled ConsoleEncoding:GetPreamble():ref:this [Tier0, IL size=6, code size=27] 226: JIT compiled Array:Empty():ref [Tier0, IL size=6, code size=49] 227: JIT compiled EmptyArray`1:.cctor() [Tier0, IL size=12, code size=52] 228: JIT compiled ReadOnlySpan`1:op_Implicit(ref):ReadOnlySpan`1 [Tier0, IL size=7, code size=79] 229: JIT compiled ReadOnlySpan`1:.ctor(ref):this [Tier0, IL size=33, code size=81] 230: JIT compiled MemoryMarshal:GetArrayDataReference(ref):byref [Tier0, IL size=7, code size=24] 231: JIT compiled ConsoleEncoding:GetMaxByteCount(int):int:this [Tier0, IL size=13, code size=63] 232: JIT compiled UTF8EncodingSealed:GetMaxByteCount(int):int:this [Tier0, IL size=20, code size=50] 233: JIT compiled Span`1:.ctor(long,int):this [Tier0, IL size=51, code size=115] 234: JIT compiled ReadOnlySpan`1:.ctor(ref,int,int):this [Tier0, IL size=65, code size=147] 235: JIT compiled Encoder:GetBytes(ReadOnlySpan`1,Span`1,bool):int:this [Tier0, IL size=44, code size=234] 236: JIT compiled MemoryMarshal:GetNonNullPinnableReference(ReadOnlySpan`1):byref [Tier0, IL size=30, code size=54] 237: JIT compiled ReadOnlySpan`1:get_Length():int:this [Tier0, IL size=7, code size=17] 238: JIT compiled MemoryMarshal:GetNonNullPinnableReference(Span`1):byref [Tier0, IL size=30, code size=54] 239: JIT compiled Span`1:get_Length():int:this [Tier0, IL size=7, code size=17] 240: JIT compiled EncoderNLS:GetBytes(long,int,long,int,bool):int:this [Tier0, IL size=92, code size=279] 241: JIT compiled ArgumentNullException:ThrowIfNull(long,String) [Tier0, IL size=12, code size=45] 242: JIT compiled Encoding:GetBytes(long,int,long,int,EncoderNLS):int:this [Tier0, IL size=57, code size=187] 243: JIT compiled EncoderNLS:get_HasLeftoverData():bool:this [Tier0, IL size=35, code size=105] 244: JIT compiled UTF8Encoding:GetBytesFast(long,int,long,int,byref):int:this [Tier0, IL size=33, code size=119] 245: JIT compiled Utf8Utility:TranscodeToUtf8(long,int,long,int,byref,byref):int [Tier0, IL size=1446, code size=3208] 246: JIT compiled Math:Min(int,int):int [Tier0, IL size=8, code size=28] 247: JIT compiled ASCIIUtility:NarrowUtf16ToAscii(long,long,long):long [Tier0, IL size=490, code size=1187] 248: JIT compiled WindowsConsoleStream:Flush():this [Tier0, IL size=26, code size=56] 249: JIT compiled ConsoleStream:Flush():this [Tier0, IL size=1, code size=10] 250: JIT compiled TextWriter:Synchronized(TextWriter):TextWriter [Tier0, IL size=28, code size=121] 251: JIT compiled SyncTextWriter:.ctor(TextWriter):this [Tier0, IL size=14, code size=52] 252: JIT compiled SyncTextWriter:WriteLine(String):this [Tier0, IL size=13, code size=140] 253: JIT compiled StreamWriter:WriteLine(String):this [Tier0, IL size=20, code size=110] 254: JIT compiled String:op_Implicit(String):ReadOnlySpan`1 [Tier0, IL size=31, code size=171] 255: JIT compiled String:GetRawStringData():byref:this [Tier0, IL size=7, code size=24] 256: JIT compiled ReadOnlySpan`1:.ctor(byref,int):this [Tier0, IL size=15, code size=39] 257: JIT compiled StreamWriter:WriteSpan(ReadOnlySpan`1,bool):this [Tier0, IL size=368, code size=1036] 258: JIT compiled MemoryMarshal:GetReference(ReadOnlySpan`1):byref [Tier0, IL size=8, code size=17] 259: JIT compiled Buffer:MemoryCopy(long,long,long,long) [Tier0, IL size=21, code size=83] 260: JIT compiled Unsafe:ReadUnaligned(long):long [Tier0, IL size=10, code size=17] 261: JIT compiled ASCIIUtility:AllCharsInUInt64AreAscii(long):bool [Tier0, IL size=16, code size=38] 262: JIT compiled ASCIIUtility:NarrowFourUtf16CharsToAsciiAndWriteToBuffer(byref,long) [Tier0, IL size=107, code size=171] 263: JIT compiled Unsafe:WriteUnaligned(byref,int) [Tier0, IL size=11, code size=22] 264: JIT compiled Unsafe:ReadUnaligned(long):int [Tier0, IL size=10, code size=16] 265: JIT compiled ASCIIUtility:AllCharsInUInt32AreAscii(int):bool [Tier0, IL size=11, code size=25] 266: JIT compiled ASCIIUtility:NarrowTwoUtf16CharsToAsciiAndWriteToBuffer(byref,int) [Tier0, IL size=24, code size=35] 267: JIT compiled Span`1:Slice(int,int):Span`1:this [Tier0, IL size=39, code size=135] 268: JIT compiled Span`1:.ctor(byref,int):this [Tier0, IL size=15, code size=39] 269: JIT compiled Span`1:op_Implicit(Span`1):ReadOnlySpan`1 [Tier0, IL size=19, code size=90] 270: JIT compiled ReadOnlySpan`1:.ctor(byref,int):this [Tier0, IL size=15, code size=39] 271: JIT compiled WindowsConsoleStream:Write(ReadOnlySpan`1):this [Tier0, IL size=35, code size=149] 272: JIT compiled WindowsConsoleStream:WriteFileNative(long,ReadOnlySpan`1,bool):int [Tier0, IL size=107, code size=272] 273: JIT compiled ReadOnlySpan`1:get_IsEmpty():bool:this [Tier0, IL size=10, code size=24] Hello, world! 274: JIT compiled AppContext:OnProcessExit() [Tier0, IL size=43, code size=161] 275: JIT compiled AssemblyLoadContext:OnProcessExit() [Tier0, IL size=101, code size=442] 276: JIT compiled EventListener:DisposeOnShutdown() [Tier0, IL size=150, code size=618] 277: JIT compiled List`1:.ctor():this [Tier0, IL size=18, code size=133] 278: JIT compiled List`1:.cctor() [Tier0, IL size=12, code size=129] 279: JIT compiled List`1:GetEnumerator():Enumerator:this [Tier0, IL size=7, code size=162] 280: JIT compiled Enumerator:.ctor(List`1):this [Tier0, IL size=39, code size=64] 281: JIT compiled Enumerator:MoveNext():bool:this [Tier0, IL size=81, code size=159] 282: JIT compiled Enumerator:get_Current():__Canon:this [Tier0, IL size=7, code size=22] 283: JIT compiled WeakReference`1:TryGetTarget(byref):bool:this [Tier0, IL size=24, code size=66] 284: JIT compiled List`1:AddWithResize(__Canon):this [Tier0, IL size=39, code size=85] 285: JIT compiled List`1:Grow(int):this [Tier0, IL size=53, code size=121] 286: JIT compiled List`1:set_Capacity(int):this [Tier0, IL size=86, code size=342] 287: JIT compiled CastHelpers:StelemRef_Helper(byref,long,Object) [Tier0, IL size=34, code size=104] 288: JIT compiled CastHelpers:StelemRef_Helper_NoCacheLookup(byref,long,Object) [Tier0, IL size=26, code size=111] 289: JIT compiled Enumerator:MoveNextRare():bool:this [Tier0, IL size=57, code size=80] 290: JIT compiled Enumerator:Dispose():this [Tier0, IL size=1, code size=14] 291: JIT compiled EventSource:Dispose():this [Tier0, IL size=14, code size=54] 292: JIT compiled EventSource:Dispose(bool):this [Tier0, IL size=124, code size=236] 293: JIT compiled EventProvider:Dispose():this [Tier0, IL size=14, code size=54] 294: JIT compiled EventProvider:Dispose(bool):this [Tier0, IL size=90, code size=230] 295: JIT compiled EventProvider:EventUnregister(long):this [Tier0, IL size=14, code size=50] 296: JIT compiled EtwEventProvider:System.Diagnostics.Tracing.IEventProvider.EventUnregister(long):int:this [Tier0, IL size=7, code size=181] 297: JIT compiled GC:SuppressFinalize(Object) [Tier0, IL size=18, code size=53] 298: JIT compiled EventPipeEventProvider:System.Diagnostics.Tracing.IEventProvider.EventUnregister(long):int:this [Tier0, IL size=13, code size=187]
有了这个,让我们继续进行实际的性能改进,从堆栈替换开始。
原文链接
Performance Improvements in .NET 7
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
如有任何疑问,请与我联系 (MingsonZheng@outlook.com)
