{"id":38524,"date":"2022-01-27T10:18:02","date_gmt":"2022-01-27T17:18:02","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=38524"},"modified":"2022-01-27T14:23:27","modified_gmt":"2022-01-27T21:23:27","slug":"performance-improvements-in-aspnet-core-6","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-aspnet-core-6\/","title":{"rendered":"Performance improvements in ASP.NET Core 6"},"content":{"rendered":"<p>Inspired by the blog posts by Stephen Toub about <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-6\/\">performance in .NET<\/a> we are writing a similar post to highlight the performance improvements done to ASP.NET Core in 6.0.<\/p>\n<h3>Benchmarking Setup<\/h3>\n<p>We will be using <a href=\"https:\/\/github.com\/dotnet\/benchmarkdotnet\">BenchmarkDotNet<\/a> for the majority of the examples throughout. A repo at <a href=\"https:\/\/github.com\/BrennanConroy\/BlogPost60Bench\">https:\/\/github.com\/BrennanConroy\/BlogPost60Bench<\/a> is provided that includes the majority of the benchmarks used in this post.<\/p>\n<p>Most of the benchmark results in this post were generated with the following command line:<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 net5.0 net6.0<\/code><\/pre>\n<p>Then selecting a specific benchmark to run from the list.<\/p>\n<p>This tells BenchmarkDotNet:<\/p>\n<ul>\n<li>Build everything in a release configuration.<\/li>\n<li>Build it targeting the .NET Framework 4.8 surface area.<\/li>\n<li>Run each benchmark on each of .NET Framework 4.8, .NET Core 3.1, .NET 5, and .NET 6.<\/li>\n<\/ul>\n<p>For some benchmarks, they were only run on .NET 6 (e.g. if comparing two ways of coding something on the same version):<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net6.0 --runtimes net6.0<\/code><\/pre>\n<p>and for others only a subset of the versions were run, e.g.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net5.0 --runtimes net5.0 net6.0<\/code><\/pre>\n<p>I&#8217;ll include the command used to run each of the benchmarks as they come up.<\/p>\n<p>Most of the results in the post were generated by running the above benchmarks on Windows, primarily so that .NET Framework 4.8 could be included in the result set. However, unless otherwise called out, in general all of these benchmarks show comparable improvements when run on Linux or on macOS. Simply ensure that you have installed each runtime you want to measure. The benchmarks were run with a <a href=\"https:\/\/github.com\/dotnet\/installer\/blob\/main\/README.md#installers-and-binaries\">nightly build of .NET 6 RC1<\/a>, along with the latest <a href=\"https:\/\/dotnet.microsoft.com\/download\">released downloads<\/a> of .NET 5 and .NET Core 3.1.<\/p>\n<h3>Span&lt;T&gt;<\/h3>\n<p>Every release since the addition of <a href=\"https:\/\/docs.microsoft.com\/archive\/msdn-magazine\/2018\/january\/csharp-all-about-span-exploring-a-new-net-mainstay\"><code>Span&lt;T&gt;<\/code> in .NET 2.1<\/a> we have converted more code to use spans both internally and as part of the public API to improve performance. This release is no exception.<\/p>\n<p>PR <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/28855\">dotnet\/aspnetcore#28855<\/a> removed a temporary string allocation in <code>PathString<\/code> coming from <code>string.SubString<\/code> when adding two <code>PathString<\/code> instances and instead uses a <code>Span&lt;char&gt;<\/code> for the temporary string. In the benchmark below we use a short string and a longer string to show the performance difference from avoiding the temporary string.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net48 --runtimes net48 net5.0 net6.0 --filter *PathStringBenchmark*<\/code><\/pre>\n<pre><code class=\"language-csharp\">private PathString _first = new PathString(\"\/first\/\");\r\nprivate PathString _second = new PathString(\"\/second\/\");\r\nprivate PathString _long = new PathString(\"\/longerpathstringtoshowsubstring\/\");\r\n\r\n[Benchmark]\r\npublic PathString AddShortString()\r\n{\r\n    return _first.Add(_second);\r\n}\r\n\r\n[Benchmark]\r\npublic PathString AddLongString()\r\n{\r\n    return _first.Add(_long);\r\n}<\/code><\/pre>\n<table style=\"width: 56.3843%;\">\n<thead>\n<tr>\n<th style=\"width: 22.028%;\">Method<\/th>\n<th style=\"width: 29.7203%;\">Runtime<\/th>\n<th style=\"width: 13.986%;\">Toolchain<\/th>\n<th style=\"text-align: right; width: 12.5874%;\">Mean<\/th>\n<th style=\"text-align: right; width: 8.04196%;\">Ratio<\/th>\n<th style=\"text-align: right; width: 13.6364%;\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 22.028%;\">AddShortString<\/td>\n<td style=\"width: 29.7203%;\">.NET Framework 4.8<\/td>\n<td style=\"width: 13.986%;\">net48<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">23.51 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">1.00<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">96 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\">AddShortString<\/td>\n<td style=\"width: 29.7203%;\">.NET 5.0<\/td>\n<td style=\"width: 13.986%;\">net5.0<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">22.73 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">0.97<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">96 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\">AddShortString<\/td>\n<td style=\"width: 29.7203%;\">.NET 6.0<\/td>\n<td style=\"width: 13.986%;\">net6.0<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">14.92 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">0.64<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">56 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\"><\/td>\n<td style=\"width: 29.7203%;\"><\/td>\n<td style=\"width: 13.986%;\"><\/td>\n<td style=\"text-align: right; width: 12.5874%;\"><\/td>\n<td style=\"text-align: right; width: 8.04196%;\"><\/td>\n<td style=\"text-align: right; width: 13.6364%;\"><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\">AddLongString<\/td>\n<td style=\"width: 29.7203%;\">.NET Framework 4.8<\/td>\n<td style=\"width: 13.986%;\">net48<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">30.89 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">1.00<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">201 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\">AddLongString<\/td>\n<td style=\"width: 29.7203%;\">.NET 5.0<\/td>\n<td style=\"width: 13.986%;\">net5.0<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">25.18 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">0.82<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">192 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 22.028%;\">AddLongString<\/td>\n<td style=\"width: 29.7203%;\">.NET 6.0<\/td>\n<td style=\"width: 13.986%;\">net6.0<\/td>\n<td style=\"text-align: right; width: 12.5874%;\">15.69 ns<\/td>\n<td style=\"text-align: right; width: 8.04196%;\">0.51<\/td>\n<td style=\"text-align: right; width: 13.6364%;\">104 B<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/34001\">dotnet\/aspnetcore#34001<\/a> introduced a new Span based API for enumerating a query string that is allocation free in a common case of no encoded characters, and lower allocations when the query string contains encoded characters.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net6.0 --runtimes net6.0 --filter *QueryEnumerableBenchmark*<\/code><\/pre>\n<pre><code class=\"language-csharp\">#if NET6_0_OR_GREATER\r\n    public enum QueryEnum\r\n    {\r\n        Simple = 1,\r\n        Encoded,\r\n    }\r\n\r\n    [ParamsAllValues]\r\n    public QueryEnum QueryParam { get; set; }\r\n\r\n    private string SimpleQueryString = \"?key1=value1&amp;key2=value2\";\r\n    private string QueryStringWithEncoding = \"?key1=valu%20&amp;key2=value%20\";\r\n\r\n    [Benchmark(Baseline  = true)]\r\n    public void QueryHelper()\r\n    {\r\n        var queryString = QueryParam == QueryEnum.Simple ? SimpleQueryString : QueryStringWithEncoding;\r\n        foreach (var queryParam in QueryHelpers.ParseQuery(queryString))\r\n        {\r\n            _ = queryParam.Key;\r\n            _ = queryParam.Value;\r\n        }\r\n    }\r\n\r\n    [Benchmark]\r\n    public void QueryEnumerable()\r\n    {\r\n        var queryString = QueryParam == QueryEnum.Simple ? SimpleQueryString : QueryStringWithEncoding;\r\n        foreach (var queryParam in new QueryStringEnumerable(queryString))\r\n        {\r\n            _ = queryParam.DecodeName();\r\n            _ = queryParam.DecodeValue();\r\n        }\r\n    }\r\n#endif<\/code><\/pre>\n<table style=\"width: 46.5291%;\">\n<thead>\n<tr>\n<th style=\"width: 32.4153%;\">Method<\/th>\n<th style=\"width: 22.8814%;\">QueryParam<\/th>\n<th style=\"text-align: right; width: 17.7966%;\">Mean<\/th>\n<th style=\"text-align: right; width: 9.74576%;\">Ratio<\/th>\n<th style=\"text-align: right; width: 16.9492%;\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 32.4153%;\">QueryHelper<\/td>\n<td style=\"width: 22.8814%;\">Simple<\/td>\n<td style=\"text-align: right; width: 17.7966%;\">243.13 ns<\/td>\n<td style=\"text-align: right; width: 9.74576%;\">1.00<\/td>\n<td style=\"text-align: right; width: 16.9492%;\">360 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 32.4153%;\">QueryEnumerable<\/td>\n<td style=\"width: 22.8814%;\">Simple<\/td>\n<td style=\"text-align: right; width: 17.7966%;\">91.43 ns<\/td>\n<td style=\"text-align: right; width: 9.74576%;\">0.38<\/td>\n<td style=\"text-align: right; width: 16.9492%;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 32.4153%;\"><\/td>\n<td style=\"width: 22.8814%;\"><\/td>\n<td style=\"text-align: right; width: 17.7966%;\"><\/td>\n<td style=\"text-align: right; width: 9.74576%;\"><\/td>\n<td style=\"text-align: right; width: 16.9492%;\"><\/td>\n<\/tr>\n<tr>\n<td style=\"width: 32.4153%;\">QueryHelper<\/td>\n<td style=\"width: 22.8814%;\">Encoded<\/td>\n<td style=\"text-align: right; width: 17.7966%;\">351.25 ns<\/td>\n<td style=\"text-align: right; width: 9.74576%;\">1.00<\/td>\n<td style=\"text-align: right; width: 16.9492%;\">432 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 32.4153%;\">QueryEnumerable<\/td>\n<td style=\"width: 22.8814%;\">Encoded<\/td>\n<td style=\"text-align: right; width: 17.7966%;\">197.59 ns<\/td>\n<td style=\"text-align: right; width: 9.74576%;\">0.56<\/td>\n<td style=\"text-align: right; width: 16.9492%;\">152 B<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>It&#8217;s important to note that there is <a href=\"https:\/\/en.wikipedia.org\/wiki\/There_ain%27t_no_such_thing_as_a_free_lunch\">no free lunch<\/a>. In the new <code>QueryStringEnumerable<\/code> API case, if you are planning on enumerating the query string values multiple times it can actually be more expensive than using <code>QueryHelpers.ParseQuery<\/code> and storing the dictionary of the parsed query string values.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/29448\">dotnet\/aspnetcore#29448<\/a> from <a href=\"https:\/\/github.com\/paulomorgado\">@paulomorgado<\/a> uses the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.string.create\">string.Create<\/a> method that allows initializing a string after it&#8217;s created if you know the final size it will be. This was used to remove some temporary string allocations in <code>UriHelper.BuildAbsolute<\/code>.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f netcoreapp3.1 --runtimes netcoreapp3.1 net6.0 --filter *UriHelperBenchmark*<\/code><\/pre>\n<pre><code class=\"language-csharp\">#if NETCOREAPP\r\n    [Benchmark]\r\n    public void BuildAbsolute()\r\n    {\r\n        _ = UriHelper.BuildAbsolute(\"https\", new HostString(\"localhost\"));\r\n    }\r\n#endif<\/code><\/pre>\n<table style=\"width: 53.5102%;\">\n<thead>\n<tr>\n<th style=\"width: 20.8103%;\">Method<\/th>\n<th style=\"width: 21.547%;\">Runtime<\/th>\n<th style=\"width: 21.7311%;\">Toolchain<\/th>\n<th style=\"text-align: right; width: 13.2597%;\">Mean<\/th>\n<th style=\"text-align: right; width: 8.28729%;\">Ratio<\/th>\n<th style=\"text-align: right; width: 14.1805%;\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 20.8103%;\">BuildAbsolute<\/td>\n<td style=\"width: 21.547%;\">.NET Core 3.1<\/td>\n<td style=\"width: 21.7311%;\">netcoreapp3.1<\/td>\n<td style=\"text-align: right; width: 13.2597%;\">92.87 ns<\/td>\n<td style=\"text-align: right; width: 8.28729%;\">1.00<\/td>\n<td style=\"text-align: right; width: 14.1805%;\">176 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 20.8103%;\">BuildAbsolute<\/td>\n<td style=\"width: 21.547%;\">.NET 6.0<\/td>\n<td style=\"width: 21.7311%;\">net6.0<\/td>\n<td style=\"text-align: right; width: 13.2597%;\">52.88 ns<\/td>\n<td style=\"text-align: right; width: 8.28729%;\">0.57<\/td>\n<td style=\"text-align: right; width: 14.1805%;\">64 B<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>PR <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31267\">dotnet\/aspnetcore#31267<\/a> converted some parsing logic in <code>ContentDispositionHeaderValue<\/code> to use <code>Span&lt;T&gt;<\/code> based APIs to avoid temporary strings and a temporary <code>byte[]<\/code> in common cases.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 net5.0 net6.0 --filter *ContentDispositionBenchmark*<\/code><\/pre>\n<pre><code class=\"language-csharp\">[Benchmark]\r\npublic void ParseContentDispositionHeader()\r\n{\r\n    var contentDisposition = new ContentDispositionHeaderValue(\"inline\");\r\n    contentDisposition.FileName = \"File\u00c3Name.bat\";\r\n}<\/code><\/pre>\n<table style=\"width: 66.3926%;\">\n<thead>\n<tr>\n<th style=\"width: 30.5638%;\">Method<\/th>\n<th style=\"width: 24.184%;\">Runtime<\/th>\n<th style=\"width: 17.0623%;\">Toolchain<\/th>\n<th style=\"text-align: right; width: 10.3858%;\">Mean<\/th>\n<th style=\"text-align: right; width: 6.52819%;\">Ratio<\/th>\n<th style=\"text-align: right; width: 11.1276%;\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 30.5638%;\">ContentDispositionHeader<\/td>\n<td style=\"width: 24.184%;\">.NET Framework 4.8<\/td>\n<td style=\"width: 17.0623%;\">net48<\/td>\n<td style=\"text-align: right; width: 10.3858%;\">654.9 ns<\/td>\n<td style=\"text-align: right; width: 6.52819%;\">1.00<\/td>\n<td style=\"text-align: right; width: 11.1276%;\">570 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 30.5638%;\">ContentDispositionHeader<\/td>\n<td style=\"width: 24.184%;\">.NET Core 3.1<\/td>\n<td style=\"width: 17.0623%;\">netcoreapp3.1<\/td>\n<td style=\"text-align: right; width: 10.3858%;\">581.5 ns<\/td>\n<td style=\"text-align: right; width: 6.52819%;\">0.89<\/td>\n<td style=\"text-align: right; width: 11.1276%;\">536 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 30.5638%;\">ContentDispositionHeader<\/td>\n<td style=\"width: 24.184%;\">.NET 5.0<\/td>\n<td style=\"width: 17.0623%;\">net5.0<\/td>\n<td style=\"text-align: right; width: 10.3858%;\">519.2 ns<\/td>\n<td style=\"text-align: right; width: 6.52819%;\">0.79<\/td>\n<td style=\"text-align: right; width: 11.1276%;\">536 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 30.5638%;\">ContentDispositionHeader<\/td>\n<td style=\"width: 24.184%;\">.NET 6.0<\/td>\n<td style=\"width: 17.0623%;\">net6.0<\/td>\n<td style=\"text-align: right; width: 10.3858%;\">295.4 ns<\/td>\n<td style=\"text-align: right; width: 6.52819%;\">0.45<\/td>\n<td style=\"text-align: right; width: 11.1276%;\">312 B<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>Idle Connections<\/h3>\n<p>One of the major components of ASP.NET Core is hosting a server which brings with it a host of different problems to optimize for. We&#8217;ll focus on improvements to idle connections in 6.0 where we made many changes to reduce the amount a memory used when a connection is waiting for data.<\/p>\n<p>There were three distinct types of changes we made, one was to reduce the size of the objects used by connections, this includes System.IO.Pipelines, SocketConnections, and SocketSenders. The second type of change was to pool commonly accessed objects so we can reuse old instances and save on allocations. The third type of change was to take advantage of something called &#8220;zero byte reads&#8221;. This is where we try to read from the connection with a zero byte buffer, if there is data available the read will return with no data, but we will know there is now data available and can provide a buffer to read that data immediately. This avoids allocating a buffer up front for a read that may complete at a future time, so we can avoid a large allocation until we know data is available.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/49270\">dotnet\/runtime#49270<\/a> reduced the size of System.IO.Pipelines from ~560 bytes to ~368 bytes which is a 34% size reduction, there are at least 2 pipes per connection so this was a great win. <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31308\">dotnet\/aspnetcore#31308<\/a> refactored the Socket layer of Kestrel to avoid a few async state machines and reduce the size of remaining state machines to get a ~33% allocation savings for each connection.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30769\">dotnet\/aspnetcore#30769<\/a> removed a per connection <code>PipeOptions<\/code> allocation and moved the allocation to the connection factory so we only allocate one for the entire lifetime of the server and reuse the same options for every connection. <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31311\">dotnet\/aspnetcore#31311<\/a> from <a href=\"https:\/\/github.com\/benaadams\">@benaadams<\/a> replaced well known header values in WebSocket requests with <a href=\"https:\/\/wikipedia.org\/wiki\/String_interning\">interned strings<\/a> which allowed the strings allocated during header parsing to be garbage collected, reducing the memory usage of the long lived WebSocket connections. <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30771\">dotnet\/aspnetcore#30771<\/a> refactored the Sockets layer in Kestrel to first avoid allocating a SocketReceiver object + a SocketAwaitableEventArgs and combine it into a single object, that saved a few bytes and resulted in less unique objects allocated per connection. That PR also pooled the SocketSender class so instead of creating one per connection you now on average have number of cores SocketSender. So in the below benchmark when we have 10,000 connections there are only 16 allocated on my machine instead of 10,000 which is a savings of ~46 MB!<\/p>\n<p>Another similar sized change is <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/49123\">dotnet\/runtime#49123<\/a> which adds support for zero-byte reads in <code>SslStream<\/code> so our 10,000 idle connections go from ~46 MB to ~2.3 MB from <code>SslStream<\/code> allocations. <a href=\"https:\/\/github.com\/dotnet\/runtime\/pull\/49117\">dotnet\/runtime#49117<\/a> added support for zero-byte reads on <code>StreamPipeReader<\/code> which was then used by Kestrel in <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30863\">dotnet\/aspnetcore#30863<\/a> to start using the zero-byte reads in <code>SslStream<\/code>.<\/p>\n<p>The culmination of all these changes is a massive reduction in memory usage for idle connections.<\/p>\n<p>The following numbers are not from a BenchmarkDotNet app as it&#8217;s measuring idle connections and it was easier to setup with a client and server application.<\/p>\n<p>Console and WebApplication code are pasted in the following gist:\n<a href=\"https:\/\/gist.github.com\/BrennanConroy\/02e8459d63305b4acaa0a021686f54c7\">https:\/\/gist.github.com\/BrennanConroy\/02e8459d63305b4acaa0a021686f54c7<\/a><\/p>\n<p>Below is the amount of memory 10,000 idle secure WebSocket connections (WSS) take on the server on different frameworks.<\/p>\n<table style=\"width: 22.0693%; height: 138px;\">\n<thead>\n<tr style=\"height: 54px;\">\n<th style=\"height: 54px; width: 4.46429%;\">Framework<\/th>\n<th style=\"text-align: right; height: 54px; width: 2.67857%;\">Memory<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr style=\"height: 28px;\">\n<td style=\"height: 28px; width: 4.46429%;\">net48<\/td>\n<td style=\"text-align: right; height: 28px; width: 2.67857%;\">665.4 MB<\/td>\n<\/tr>\n<tr style=\"height: 28px;\">\n<td style=\"height: 28px; width: 4.46429%;\">net5.0<\/td>\n<td style=\"text-align: right; height: 28px; width: 2.67857%;\">603.1 MB<\/td>\n<\/tr>\n<tr style=\"height: 28px;\">\n<td style=\"height: 28px; width: 4.46429%;\">net6.0<\/td>\n<td style=\"text-align: right; height: 28px; width: 2.67857%;\">160.8 MB<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>That&#8217;s an almost 4x memory reduction from net5.0 to net6.0!<\/p>\n<h3>Entity Framework Core<\/h3>\n<p>EF Core made some massive improvements in 6.0, it is 31% faster at executing queries and the <a href=\"https:\/\/www.techempower.com\/benchmarks\/#section=data-r20\">TechEmpower Fortunes benchmark<\/a> improved by 70% with Runtime updates, optimized benchmarks and the EF improvements.<\/p>\n<p>These improvements came from improving object pooling, intelligently checking if telemetry is enabled, and adding an option to opt out of thread safety checks when you know your app uses DbContext safely.<\/p>\n<p>See the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-entity-framework-core-6-0-preview-4-performance-edition\/\">Announcing Entity Framework Core 6.0 Preview 4: Performance Edition<\/a> blog post which highlights many of the improvements in detail.<\/p>\n<h3>Blazor<\/h3>\n<h5>Native <code>byte[]<\/code> Interop<\/h5>\n<p>Blazor now has efficient support for byte arrays when performing JavaScript interop. Previously, byte arrays sent to and from JavaScript were Base64 encoded so they could be serialized as JSON, which increased the transfer size and the CPU load. The Base64 encoding has now been optimized away in .NET 6 allowing users to transparently work with <code>byte[]<\/code> in .NET and <code>Uint8Array<\/code> in JavaScript. Documentation on using this feature for <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/javascript-interoperability\/call-javascript-from-dotnet?view=aspnetcore-6.0#byte-array-support\">JavaScript to .NET<\/a> and <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/javascript-interoperability\/call-dotnet-from-javascript?view=aspnetcore-6.0#byte-array-support\">.NET to JavaScript<\/a>.<\/p>\n<p>Let&#8217;s take a look at a quick benchmark to see the difference between <code>byte[]<\/code> interop in .NET 5 and .NET 6. The following Razor code creates a 22 kB <code>byte[]<\/code>, and sends it to a JavaScript <code>receiveAndReturnBytes<\/code> function, which immediately returns the <code>byte[]<\/code>. This roundtrip of data is repeated 10,000 times and the time data is printed to the screen. This code is the same for .NET 5 and .NET 6.<\/p>\n<pre><code class=\"language-razor\">&lt;button @onclick=\"@RoundtripData\"&gt;Roundtrip Data&lt;\/button&gt;\r\n\r\n&lt;hr \/&gt;\r\n\r\n@Message\r\n\r\n@code {\r\n    public string Message { get; set; } = \"Press button to benchmark\";\r\n\r\n    private async Task RoundtripData()\r\n    {\r\n        var bytes = new byte[1024*22];\r\n        List&lt;double&gt; timeForInterop = new List&lt;double&gt;();\r\n        var testTime = DateTime.Now;\r\n\r\n        for (var i = 0; i &lt; 10_000; i++)\r\n        {\r\n            var interopTime = DateTime.Now;\r\n\r\n            var result = await JSRuntime.InvokeAsync&lt;byte[]&gt;(\"receiveAndReturnBytes\", bytes);\r\n\r\n            timeForInterop.Add(DateTime.Now.Subtract(interopTime).TotalMilliseconds);\r\n        }\r\n\r\n        Message = $\"Round-tripped: {bytes.Length \/ 1024d} kB 10,000 times and it took on average {timeForInterop.Average():F3}ms, and in total {DateTime.Now.Subtract(testTime).TotalMilliseconds:F1}ms\";\r\n    }\r\n}<\/code><\/pre>\n<p>Next we take a look at the <code>receiveAndReturnBytes<\/code> JavaScript function. In .NET 5. We must first decode the Base64 encoded byte array into a <code>Uint8Array<\/code> so it may be used in application code. Then we must re-encode it into Base64 before returning the data to the server.<\/p>\n<pre><code class=\"language-javascript\">function receiveAndReturnBytes(bytesReceivedBase64Encoded) {\r\n    const bytesReceived = base64ToArrayBuffer(bytesReceivedBase64Encoded);\r\n\r\n    \/\/ Use Uint8Array data in application\r\n\r\n    const bytesToSendBase64Encoded = base64EncodeByteArray(bytesReceived);\r\n\r\n    if (bytesReceivedBase64Encoded != bytesToSendBase64Encoded) {\r\n        throw new Error(\"Expected input\/output to match.\")\r\n    }\r\n\r\n    return bytesToSendBase64Encoded;\r\n}\r\n\r\n\/\/ https:\/\/stackoverflow.com\/a\/21797381\r\nfunction base64ToArrayBuffer(base64) {\r\n    const binaryString = atob(base64);\r\n    const length = binaryString.length;\r\n    const result = new Uint8Array(length);\r\n    for (let i = 0; i &lt; length; i++) {\r\n        result[i] = binaryString.charCodeAt(i);\r\n    }\r\n    return result;\r\n}\r\n\r\nfunction base64EncodeByteArray(data) {\r\n    const charBytes = new Array(data.length);\r\n    for (var i = 0; i &lt; data.length; i++) {\r\n        charBytes[i] = String.fromCharCode(data[i]);\r\n    }\r\n    const dataBase64Encoded = btoa(charBytes.join(''));\r\n    return dataBase64Encoded;\r\n}<\/code><\/pre>\n<p>The encoded\/decoding adds significant overhead both on the client and server, along with requiring extensive boiler plate code as well. So how would this be done in .NET 6? Well, it&#8217;s quite a bit simpler:<\/p>\n<pre><code class=\"language-javascript\">function receiveAndReturnBytes(bytesReceived) {\r\n    \/\/ bytesReceived comes as a Uint8Array ready for use\r\n    \/\/ and can be used by the application or immediately returned.\r\n    return bytesReceived;\r\n}<\/code><\/pre>\n<p>So it&#8217;s definitely easier to write, but how does it perform? Running these snippets in a <code>blazorserver<\/code> template in .NET 5 and .NET 6 respectively, under <code>Release<\/code> configuration, we see .NET 6 offers a 78% performance improvement in <code>byte[]<\/code> interop!<\/p>\n<table>\n<thead>\n<tr>\n<th>&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;<\/th>\n<th>.NET 6 (ms)<\/th>\n<th>.NET 5 (ms)<\/th>\n<th>Improvement<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Total Time<\/td>\n<td>5273<\/td>\n<td>24463<\/td>\n<td>78%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Additionally, this byte array interop support is leveraged within the framework to enable bidirectional streaming interop between JavaScript and .NET. Users are now able to transport arbitrary binary data. Documentation on streaming from <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/javascript-interoperability\/call-javascript-from-dotnet?view=aspnetcore-6.0#stream-from-net-to-javascript\">.NET to JavaScript is available here<\/a>, and the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/javascript-interoperability\/call-dotnet-from-javascript?view=aspnetcore-6.0#stream-from-javascript-to-net\">JavaScript to .NET documentation is here<\/a>.<\/p>\n<h5>Input File<\/h5>\n<p>Using the Blazor Streaming Interop\u200b mentioned above, we now support <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/file-uploads?view=aspnetcore-6.0\">uploading large files via the InputFile\u200b component<\/a> (previously uploads were limited to ~2GB). This component also features significant speed improvements on account of native byte[] streaming as opposed to going through Base64 encoding. For instance, a 100 MB file is uploaded 77% quicker in comparison to .NET 5.<\/p>\n<table>\n<thead>\n<tr>\n<th>.NET 6 (ms)<\/th>\n<th>.NET 5 (ms)<\/th>\n<th>Percentage<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>2591<\/td>\n<td>10504<\/td>\n<td>75%<\/td>\n<\/tr>\n<tr>\n<td>2607<\/td>\n<td>11764<\/td>\n<td>78%<\/td>\n<\/tr>\n<tr>\n<td>2632<\/td>\n<td>11821<\/td>\n<td>78%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td>Average:<\/td>\n<td>77%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Note the streaming interop support also enables efficient downloads of (large) files, for more details, please see the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/blazor\/file-downloads?view=aspnetcore-6.0\">documentation<\/a>.<\/p>\n<p>The <code>InputFile<\/code> component was upgraded to utilize streaming via <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/33900\">dotnet\/aspnetcore#33900<\/a>.<\/p>\n<h3>Hodgepodge<\/h3>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/30320\">dotnet\/aspnetcore#30320<\/a> from <a href=\"https:\/\/github.com\/benaadams\">@benaadams<\/a> modernized our Typescript libraries and optimized them so websites load faster. The signalr.min.js file went from 36.8 kB compressed and 132 kB uncompressed, to 16.1 kB compressed and 42.2 kB uncompressed. And the blazor.server.js file 86.7 kB compressed and 276 kB uncompressed, to 43.9 kB compressed and 130 kB uncompressed.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31322\">dotnet\/aspnetcore#31322<\/a> from <a href=\"https:\/\/github.com\/benaadams\">@benaadams<\/a> removes some unnecessary casts when getting common features from the connections feature collection. This gives a ~50% improvement when accessing common features from the collection. Seeing the performance improvement in a benchmark isn&#8217;t really possible unfortunately because it requires a bunch of internal types so I&#8217;ll include the numbers from the PR here, and if you&#8217;re interested in running them, the PR includes benchmarks that can run against the internal code.<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th style=\"text-align: right;\">Mean<\/th>\n<th style=\"text-align: right;\">Op\/s<\/th>\n<th style=\"text-align: right;\">Diff<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Get&lt;IHttpRequestFeature&gt;*<\/td>\n<td style=\"text-align: right;\">8.507 ns<\/td>\n<td style=\"text-align: right;\">117,554,189.6<\/td>\n<td style=\"text-align: right;\">+50.0%<\/td>\n<\/tr>\n<tr>\n<td>Get&lt;IHttpResponseFeature&gt;*<\/td>\n<td style=\"text-align: right;\">9.034 ns<\/td>\n<td style=\"text-align: right;\">110,689,963.7<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>Get&lt;IHttpResponseBodyFeature&gt;*<\/td>\n<td style=\"text-align: right;\">9.466 ns<\/td>\n<td style=\"text-align: right;\">105,636,431.7<\/td>\n<td style=\"text-align: right;\">+58.7%<\/td>\n<\/tr>\n<tr>\n<td>Get&lt;IRouteValuesFeature&gt;*<\/td>\n<td style=\"text-align: right;\">10.007 ns<\/td>\n<td style=\"text-align: right;\">99,927,927.4<\/td>\n<td style=\"text-align: right;\">+50.0%<\/td>\n<\/tr>\n<tr>\n<td>Get&lt;IEndpointFeature&gt;*<\/td>\n<td style=\"text-align: right;\">10.564 ns<\/td>\n<td style=\"text-align: right;\">94,656,794.2<\/td>\n<td style=\"text-align: right;\">+44.7%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31519\">dotnet\/aspnetcore#31519<\/a> also from <a href=\"https:\/\/github.com\/benaadams\">@benaadams<\/a> adds <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/default-implementations-in-interfaces\/\">default interface methods<\/a> to the <code>IHeaderDictionary<\/code> type for accessing common headers via properties named after the header name. No more mistyping common headers when accessing the header dictionary! More interestingly for this blog post, this change allows server implementations to return a custom header dictionary that implements these new interface methods more optimally. For example, instead of querying an internal dictionary for a header value which requires hashing the key and looking up an entry, the server might have the header value stored directly in a field and can return the field directly. This change resulted in up to 480% improvements in some cases when getting or setting header values. Once again, to properly benchmark this change to show the improvements it requires using internal types for the setup so I will be including the numbers from the PR, and for those interested in trying it out the PR contains benchmarks that run on the internal code.<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Branch<\/th>\n<th>Type<\/th>\n<th style=\"text-align: right;\">Mean<\/th>\n<th style=\"text-align: right;\">Op\/s<\/th>\n<th style=\"text-align: right;\">Delta<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>GetHeaders<\/td>\n<td>before<\/td>\n<td>Plaintext<\/td>\n<td style=\"text-align: right;\">25.793 ns<\/td>\n<td style=\"text-align: right;\">38,770,569.6<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>GetHeaders<\/td>\n<td>after<\/td>\n<td>Plaintext<\/td>\n<td style=\"text-align: right;\">12.775 ns<\/td>\n<td style=\"text-align: right;\">78,279,480.0<\/td>\n<td style=\"text-align: right;\">+101.9%<\/td>\n<\/tr>\n<tr>\n<td>GetHeaders<\/td>\n<td>before<\/td>\n<td>Common<\/td>\n<td style=\"text-align: right;\">121.355 ns<\/td>\n<td style=\"text-align: right;\">8,240,299.3<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>GetHeaders<\/td>\n<td>after<\/td>\n<td>Common<\/td>\n<td style=\"text-align: right;\">37.598 ns<\/td>\n<td style=\"text-align: right;\">26,597,474.6<\/td>\n<td style=\"text-align: right;\">+222.8%<\/td>\n<\/tr>\n<tr>\n<td>GetHeaders<\/td>\n<td>before<\/td>\n<td>Unknown<\/td>\n<td style=\"text-align: right;\">366.456 ns<\/td>\n<td style=\"text-align: right;\">2,728,840.7<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>GetHeaders<\/td>\n<td>after<\/td>\n<td>Unknown<\/td>\n<td style=\"text-align: right;\">223.472 ns<\/td>\n<td style=\"text-align: right;\">4,474,824.0<\/td>\n<td style=\"text-align: right;\">+64.0%<\/td>\n<\/tr>\n<tr>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td style=\"text-align: right;\"><\/td>\n<td style=\"text-align: right;\"><\/td>\n<td style=\"text-align: right;\"><\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>before<\/td>\n<td>Plaintext<\/td>\n<td style=\"text-align: right;\">49.324 ns<\/td>\n<td style=\"text-align: right;\">20,273,931.8<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>after<\/td>\n<td>Plaintext<\/td>\n<td style=\"text-align: right;\">34.996 ns<\/td>\n<td style=\"text-align: right;\">28,574,778.8<\/td>\n<td style=\"text-align: right;\">+40.9%<\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>before<\/td>\n<td>Common<\/td>\n<td style=\"text-align: right;\">635.060 ns<\/td>\n<td style=\"text-align: right;\">1,574,654.3<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>after<\/td>\n<td>Common<\/td>\n<td style=\"text-align: right;\">108.041 ns<\/td>\n<td style=\"text-align: right;\">9,255,723.7<\/td>\n<td style=\"text-align: right;\">+487.7%<\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>before<\/td>\n<td>Unknown<\/td>\n<td style=\"text-align: right;\">1,439.945 ns<\/td>\n<td style=\"text-align: right;\">694,470.8<\/td>\n<td style=\"text-align: right;\">&#8211;<\/td>\n<\/tr>\n<tr>\n<td>SetHeaders<\/td>\n<td>after<\/td>\n<td>Unknown<\/td>\n<td style=\"text-align: right;\">517.067 ns<\/td>\n<td style=\"text-align: right;\">1,933,985.7<\/td>\n<td style=\"text-align: right;\">+178.4%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31466\">dotnet\/aspnetcore#31466<\/a> used the new CancellationTokenSource.TryReset() method introduced in .NET 6 to reuse CancellationTokenSource&#8217;s if a connection closed without being canceled. The below numbers were collected by running <a href=\"https:\/\/github.com\/codesenberg\/bombardier\">bombardier<\/a> against Kestrel with 125 connections and it ran for ~100,000 requests.<\/p>\n<table style=\"width: 47.6383%; height: 69px;\">\n<thead>\n<tr>\n<th style=\"width: 13.602%;\">Branch<\/th>\n<th style=\"width: 46.3476%;\">Type<\/th>\n<th style=\"width: 20.1511%;\">Allocations<\/th>\n<th style=\"width: 17.3944%;\">Bytes<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 13.602%;\">Before<\/td>\n<td style=\"width: 46.3476%;\">CancellationTokenSource<\/td>\n<td style=\"width: 20.1511%;\">98,314<\/td>\n<td style=\"width: 17.3944%;\">4,719,072<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 13.602%;\">After<\/td>\n<td style=\"width: 46.3476%;\">CancellationTokenSource<\/td>\n<td style=\"width: 20.1511%;\">125<\/td>\n<td style=\"width: 17.3944%;\">6,000<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31528\">dotnet\/aspnetcore#31528<\/a> and <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/34075\">dotnet\/aspnetcore#34075<\/a> made similar changes for reusing <code>CancellationTokenSource<\/code>&#8216;s for HTTPS handshakes and HTTP3 streams respectively.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31660\">dotnet\/aspnetcore#31660<\/a> improved the perf of <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/signalr\/streaming?view=aspnetcore-5.0#server-to-client-streaming\">server to client<\/a> streaming in SignalR by reusing the allocated <code>StreamItem<\/code> object for the whole stream instead of allocating one per stream item. And <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31661\">dotnet\/aspnetcore#31661<\/a> stores the <code>HubCallerClients<\/code> object on the SignalR connection instead of allocating it per Hub method call.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31506\">dotnet\/aspnetcore#31506<\/a> from <a href=\"https:\/\/github.com\/ShreyasJejurkar\">@ShreyasJejurkar<\/a> refactored the internals of the WebSocket handshake to avoid a temporary <code>List&lt;T&gt;<\/code> allocation. <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/32829\">dotnet\/aspnetcore#32829<\/a> from <a href=\"https:\/\/github.com\/gfoidl\">@gfoidl<\/a> refactored <code>QueryCollection<\/code> to reduce allocations and vectorize some of the code. <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/32234\">dotnet\/aspnetcore#32234<\/a> from <a href=\"https:\/\/github.com\/benaadams\">@benaadams<\/a> removed an unused field in <code>HttpRequestHeaders<\/code> enumeration which improves the perf by no longer assigning to the field for every header enumerated.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31333\">dotnet\/aspnetcore#31333<\/a> from <a href=\"https:\/\/github.com\/martincostello\">martincostello<\/a> converted Http.Sys to use <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/fundamentals\/logging\/loggermessage?view=aspnetcore-5.0\"><code>LoggerMessage.Define<\/code><\/a> which is the high performance logging API. This avoids unnecessary boxing of value types, parsing of the logging format string, and in some cases avoids allocations of strings or objects when the log level isn&#8217;t enabled.<\/p>\n<p><a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/pull\/31784\">dotnet\/aspnetcore#31784<\/a> adds a new <code>IApplicationBuilder.Use<\/code> overload for registering middleware that avoids some unnecessary per-request allocations when running the middleware.\nOld code looks like:<\/p>\n<pre><code class=\"language-csharp\">app.Use(async (context, next) =&gt;\r\n{\r\n    await next();\r\n});<\/code><\/pre>\n<p>New code looks like:<\/p>\n<pre><code class=\"language-csharp\">app.Use(async (context, next) =&gt;\r\n{\r\n    await next(context);\r\n});<\/code><\/pre>\n<p>The below benchmark simulates the middleware pipeline without setting up a server to showcase the improvement. An <code>int<\/code> is used instead of <code>HttpContext<\/code> for a request and the middleware returns a completed task.<\/p>\n<pre><code class=\"language-console\">dotnet run -c Release -f net6.0 --runtimes net6.0 --filter *UseMiddlewareBenchmark*<\/code><\/pre>\n<pre><code class=\"language-csharp\">static private Func&lt;Func&lt;int, Task&gt;, Func&lt;int, Task&gt;&gt; UseOld(Func&lt;int, Func&lt;Task&gt;, Task&gt; middleware)\r\n{\r\n    return next =&gt;\r\n    {\r\n        return context =&gt;\r\n        {\r\n            Func&lt;Task&gt; simpleNext = () =&gt; next(context);\r\n            return middleware(context, simpleNext);\r\n        };\r\n    };\r\n}\r\n\r\nstatic private Func&lt;Func&lt;int, Task&gt;, Func&lt;int, Task&gt;&gt; UseNew(Func&lt;int, Func&lt;int, Task&gt;, Task&gt; middleware)\r\n{\r\n    return next =&gt; context =&gt; middleware(context, next);\r\n}\r\n\r\nFunc&lt;int, Task&gt; Middleware = UseOld((c, n) =&gt; n())(i =&gt; Task.CompletedTask);\r\nFunc&lt;int, Task&gt; NewMiddleware = UseNew((c, n) =&gt; n(c))(i =&gt; Task.CompletedTask);\r\n\r\n[Benchmark(Baseline = true)]\r\npublic Task Use()\r\n{\r\n    return Middleware(10);\r\n}\r\n\r\n[Benchmark]\r\npublic Task UseNew()\r\n{\r\n    return NewMiddleware(10);\r\n}<\/code><\/pre>\n<table style=\"width: 31.6173%;\">\n<thead>\n<tr>\n<th style=\"width: 25.8567%;\">Method<\/th>\n<th style=\"text-align: right; width: 29.595%;\">Mean<\/th>\n<th style=\"text-align: right; width: 16.1994%;\">Ratio<\/th>\n<th style=\"text-align: right; width: 28.0374%;\">Allocated<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 25.8567%;\">Use<\/td>\n<td style=\"text-align: right; width: 29.595%;\">15.832 ns<\/td>\n<td style=\"text-align: right; width: 16.1994%;\">1.00<\/td>\n<td style=\"text-align: right; width: 28.0374%;\">96 B<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 25.8567%;\">UseNew<\/td>\n<td style=\"text-align: right; width: 29.595%;\">2.592 ns<\/td>\n<td style=\"text-align: right; width: 16.1994%;\">0.16<\/td>\n<td style=\"text-align: right; width: 28.0374%;\">&#8211;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Summary<\/h2>\n<p>I hope you enjoyed reading about some of the improvements made in ASP.NET Core 6.0! And I encourage you to take a look at the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/performance-improvements-in-net-6\/\">performance improvements in .NET 6<\/a> blog post that goes over performance in the Runtime.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>ASP.NET Core in .NET 6 brought several performance improvements. In this article I cover some top improvements and why now is a great time to upgrade to .NET 6.<\/p>\n","protected":false},"author":82107,"featured_media":38525,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,197,7509],"tags":[32,108],"class_list":["post-38524","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnet","category-aspnetcore","tag-asp-net-core","tag-performance"],"acf":[],"blog_post_summary":"<p>ASP.NET Core in .NET 6 brought several performance improvements. In this article I cover some top improvements and why now is a great time to upgrade to .NET 6.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/38524","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/82107"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=38524"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/38524\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/38525"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=38524"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=38524"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=38524"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}