【译】ASP.NET Core 6 中的性能改进 (2)

重要的是要注意没有免费的午餐。在新的 QueryStringEnumerable API 案例中,如果您计划多次枚举查询字符串值,它实际上可能比使用 QueryHelpers.ParseQuery 并存储已解析查询字符串值的字典更昂贵。

@paulomorgado 的 dotnet/aspnetcore#29448 使用 string.Create 方法,如果您知道字符串的最终大小,则该方法允许在创建字符串后对其进行初始化。这用于删除 UriHelper.BuildAbsolute 中的一些临时字符串分配。

dotnet run -c Release -f netcoreapp3.1 --runtimes netcoreapp3.1 net6.0 --filter *UriHelperBenchmark* #if NETCOREAPP [Benchmark] public void BuildAbsolute() { _ = UriHelper.BuildAbsolute("https", new HostString("localhost")); } #endif Method Runtime Toolchain Mean Ratio Allocated
BuildAbsolute   .NET Core 3.1   netcoreapp3.1   92.87 ns   1.00   176 B  
BuildAbsolute   .NET 6.0   net6.0   52.88 ns   0.57   64 B  

PR dotnet/aspnetcore#31267将 ContentDispositionHeaderValue 中的一些解析逻辑转换为使用基于 Span 的 API,以避免在常见情况下出现临时字符串和临时 byte[]。

dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 net5.0 net6.0 --filter *ContentDispositionBenchmark* [Benchmark] public void ParseContentDispositionHeader() { var contentDisposition = new ContentDispositionHeaderValue("inline"); contentDisposition.FileName = "FileÃName.bat"; } Method Runtime Toolchain Mean Ratio Allocated
ContentDispositionHeader   .NET Framework 4.8   net48   654.9 ns   1.00   570 B  
ContentDispositionHeader   .NET Core 3.1   netcoreapp3.1   581.5 ns   0.89   536 B  
ContentDispositionHeader   .NET 5.0   net5.0   519.2 ns   0.79   536 B  
ContentDispositionHeader   .NET 6.0   net6.0   295.4 ns   0.45   312 B  
空闲连接

ASP.NET Core 的主要组件之一是托管服务器,它带来了许多需要优化的不同问题。我们将专注于改进 6.0 中的空闲连接,我们在其中进行了许多更改以减少连接等待数据时使用的内存量。

我们进行了三种不同类型的更改,一种是减少连接使用的对象的大小,包括 System.IO.Pipelines、SocketConnections 和 SocketSenders。第二种类型的更改是汇集常用访问的对象,以便我们可以重用旧实例并节省分配。第三种变化是利用所谓的“零字节读取”。这是我们尝试使用零字节缓冲区从连接中读取的地方,如果有可用数据,则读取将返回没有数据,但我们会知道现在有可用数据,并且可以提供一个缓冲区来立即读取该数据。这避免了为可能在将来完成的读取预先分配缓冲区,因此我们可以避免大量分配,直到我们知道数据可用。

dotnet/runtime#49270 将 System.IO.Pipelines 的大小从 ~560 字节减少到 ~368 字节,这减少了 34%,每个连接至少有 2 个管道,所以这是一个巨大的胜利。

dotnet/aspnetcore#31308 重构了 Kestrel 的 Socket 层,以避免一些异步状态机并减少剩余状态机的大小,从而为每个连接节省约 33% 的分配。

dotnet/aspnetcore#30769 删除了每个连接的 PipeOptions 分配并将分配移至连接工厂,因此我们仅在服务器的整个生命周期内分配一个,并为每个连接重用相同的选项。来自@benaadams 的 dotnet/aspnetcore#31311 将 WebSocket 请求中众所周知的标头值替换为内部字符串,这允许在标头解析期间分配的字符串被垃圾收集,从而减少长期 WebSocket 连接的内存使用量。 dotnet/aspnetcore#30771 重构了 Kestrel 中的 Sockets 层,首先避免分配 SocketReceiver 对象 + SocketAwaitableEventArgs 并将其组合成一个对象,这节省了几个字节并导致每个连接分配的唯一对象更少。该 PR 还汇集了 SocketSender 类,因此您现在平均拥有多个核心 SocketSender,而不是为每个连接创建一个。所以在下面的基准测试中,当我们有 10,000 个连接时,我的机器上只分配了 16 个,而不是 10,000 个,这节省了约 46 MB!

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgzfdg.html