From cef88411aecd9678b03b840d9cf1671295f57657 Mon Sep 17 00:00:00 2001 From: Brennan Date: Mon, 21 Oct 2024 17:26:36 -0700 Subject: [PATCH 01/10] Remove some allocs --- .../Middleware/MiddlewareHelpers.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 4aff93401..938d6796d 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.Text; @@ -37,12 +39,14 @@ public static Task RenderFortunesHtml(IEnumerable model, HttpContext ht var sb = StringBuilderCache.Acquire(); sb.Append("Fortunes"); + + Span buffer = stackalloc char[256]; foreach (var item in model) { sb.Append(""); } @@ -51,6 +55,18 @@ public static Task RenderFortunesHtml(IEnumerable model, HttpContext ht // fortunes includes multibyte characters so response.Length is incorrect httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(response); return httpContext.Response.WriteAsync(response); + + static void Encode(StringBuilder sb, HtmlEncoder htmlEncoder, string item) + { + Span buffer = stackalloc char[256]; + int remaining = item.Length; + do + { + htmlEncoder.Encode(item.AsSpan()[..remaining], buffer, out var consumed, out var written, isFinalBlock: true); + remaining -= consumed; + sb.Append(buffer.Slice(0, written)); + } while (remaining != 0); + } } } } From 24d6241d195aece5384ad324078e9334476c21dd Mon Sep 17 00:00:00 2001 From: Brennan Date: Tue, 22 Oct 2024 10:41:27 -0700 Subject: [PATCH 02/10] Write to Pipe --- .../Middleware/MiddlewareHelpers.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 938d6796d..a5f319a6d 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Collections.Generic; using System.Globalization; +using System.IO.Pipelines; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -32,31 +33,27 @@ public static int GetMultipleQueriesQueryCount(HttpContext httpContext) : 1; } - public static Task RenderFortunesHtml(IEnumerable model, HttpContext httpContext, HtmlEncoder htmlEncoder) + public static async Task RenderFortunesHtml(IEnumerable model, HttpContext httpContext, HtmlEncoder htmlEncoder) { httpContext.Response.StatusCode = StatusCodes.Status200OK; httpContext.Response.ContentType = "text/html; charset=UTF-8"; - var sb = StringBuilderCache.Acquire(); - sb.Append("Fortunes
idmessage
"); - sb.Append(item.Id.ToString(CultureInfo.InvariantCulture)); + sb.Append(CultureInfo.InvariantCulture, $"{item.Id}"); sb.Append(""); - sb.Append(htmlEncoder.Encode(item.Message)); + Encode(sb, htmlEncoder, item.Message); sb.Append("
"); + Encoding.UTF8.GetBytes("Fortunes
idmessage
", httpContext.Response.BodyWriter); - Span buffer = stackalloc char[256]; foreach (var item in model) { - sb.Append(""); + Encoding.UTF8.GetBytes("", httpContext.Response.BodyWriter); } - sb.Append("
idmessage
"); - sb.Append(CultureInfo.InvariantCulture, $"{item.Id}"); - sb.Append(""); - Encode(sb, htmlEncoder, item.Message); - sb.Append("
", httpContext.Response.BodyWriter); + Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), httpContext.Response.BodyWriter); + Encoding.UTF8.GetBytes("", httpContext.Response.BodyWriter); + EncodeToPipe(httpContext.Response.BodyWriter, htmlEncoder, item.Message); + Encoding.UTF8.GetBytes("
"); - var response = StringBuilderCache.GetStringAndRelease(sb); - // fortunes includes multibyte characters so response.Length is incorrect - httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(response); - return httpContext.Response.WriteAsync(response); + Encoding.UTF8.GetBytes("", httpContext.Response.BodyWriter); - static void Encode(StringBuilder sb, HtmlEncoder htmlEncoder, string item) + await httpContext.Response.BodyWriter.FlushAsync(); + + static void EncodeToPipe(PipeWriter writer, HtmlEncoder htmlEncoder, string item) { Span buffer = stackalloc char[256]; int remaining = item.Length; @@ -64,7 +61,7 @@ static void Encode(StringBuilder sb, HtmlEncoder htmlEncoder, string item) { htmlEncoder.Encode(item.AsSpan()[..remaining], buffer, out var consumed, out var written, isFinalBlock: true); remaining -= consumed; - sb.Append(buffer.Slice(0, written)); + Encoding.UTF8.GetBytes(buffer.Slice(0, written), writer); } while (remaining != 0); } } From 50b1bda1fa826a4ec10cb13940f47c9de09a7b98 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 11:56:37 -0700 Subject: [PATCH 03/10] start --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index a5f319a6d..29edfd351 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -38,7 +38,11 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont httpContext.Response.StatusCode = StatusCodes.Status200OK; httpContext.Response.ContentType = "text/html; charset=UTF-8"; - Encoding.UTF8.GetBytes("Fortunes", httpContext.Response.BodyWriter); + await httpContext.Response.StartAsync(); + + Encoding.UTF8.GetBytes( + "Fortunes
idmessage
", + httpContext.Response.BodyWriter); foreach (var item in model) { From e405b309139618804449aaad9a36d81f41f550e7 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 12:10:53 -0700 Subject: [PATCH 04/10] span --- .../Middleware/MiddlewareHelpers.cs | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 29edfd351..f39bfb97e 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -40,24 +40,26 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont await httpContext.Response.StartAsync(); + var writer = new BufferWriter(httpContext.Response.BodyWriter); + Encoding.UTF8.GetBytes( "Fortunes
idmessage
", - httpContext.Response.BodyWriter); + writer); foreach (var item in model) { - Encoding.UTF8.GetBytes("", httpContext.Response.BodyWriter); + Encoding.UTF8.GetBytes("", writer); } - Encoding.UTF8.GetBytes("
idmessage
", httpContext.Response.BodyWriter); - Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), httpContext.Response.BodyWriter); - Encoding.UTF8.GetBytes("", httpContext.Response.BodyWriter); - EncodeToPipe(httpContext.Response.BodyWriter, htmlEncoder, item.Message); - Encoding.UTF8.GetBytes("
", writer); + Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), writer); + Encoding.UTF8.GetBytes("", writer); + EncodeToPipe(writer, htmlEncoder, item.Message); + Encoding.UTF8.GetBytes("
", httpContext.Response.BodyWriter); + Encoding.UTF8.GetBytes("", writer); await httpContext.Response.BodyWriter.FlushAsync(); - static void EncodeToPipe(PipeWriter writer, HtmlEncoder htmlEncoder, string item) + static void EncodeToPipe(IBufferWriter writer, HtmlEncoder htmlEncoder, string item) { Span buffer = stackalloc char[256]; int remaining = item.Length; @@ -70,4 +72,35 @@ static void EncodeToPipe(PipeWriter writer, HtmlEncoder htmlEncoder, string item } } } + + internal class BufferWriter : IBufferWriter + { + private readonly IBufferWriter _inner; + private Memory _memory; + + public BufferWriter(IBufferWriter writer) + { + _inner = writer; + } + + public void Advance(int count) + { + _memory = _memory.Slice(count); + _inner.Advance(count); + } + + public Memory GetMemory(int sizeHint = 0) + { + if (_memory.Length == 0 || _memory.Length < sizeHint) + { + _memory = _inner.GetMemory(sizeHint); + } + return _memory; + } + + public Span GetSpan(int sizeHint = 0) + { + return GetMemory(sizeHint).Span; + } + } } From ecb6d6d9376eb03a5c215fd99d815fc9b30551ba Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 13:19:03 -0700 Subject: [PATCH 05/10] struct --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index f39bfb97e..97aa8ba99 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -51,7 +51,7 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont Encoding.UTF8.GetBytes("", writer); Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), writer); Encoding.UTF8.GetBytes("", writer); - EncodeToPipe(writer, htmlEncoder, item.Message); + EncodeToPipe(ref writer, htmlEncoder, item.Message); Encoding.UTF8.GetBytes("", writer); } @@ -59,7 +59,7 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont await httpContext.Response.BodyWriter.FlushAsync(); - static void EncodeToPipe(IBufferWriter writer, HtmlEncoder htmlEncoder, string item) + static void EncodeToPipe(ref BufferWriter writer, HtmlEncoder htmlEncoder, string item) { Span buffer = stackalloc char[256]; int remaining = item.Length; @@ -73,7 +73,7 @@ static void EncodeToPipe(IBufferWriter writer, HtmlEncoder htmlEncoder, st } } - internal class BufferWriter : IBufferWriter + internal struct BufferWriter : IBufferWriter { private readonly IBufferWriter _inner; private Memory _memory; From 622d7041181c7a2835f89dbffd261cc3d3dd0c07 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 13:45:18 -0700 Subject: [PATCH 06/10] no struct --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 97aa8ba99..0871258ee 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -51,7 +51,7 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont Encoding.UTF8.GetBytes("", writer); Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), writer); Encoding.UTF8.GetBytes("", writer); - EncodeToPipe(ref writer, htmlEncoder, item.Message); + EncodeToPipe(writer, htmlEncoder, item.Message); Encoding.UTF8.GetBytes("", writer); } @@ -59,7 +59,7 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont await httpContext.Response.BodyWriter.FlushAsync(); - static void EncodeToPipe(ref BufferWriter writer, HtmlEncoder htmlEncoder, string item) + static void EncodeToPipe(BufferWriter writer, HtmlEncoder htmlEncoder, string item) { Span buffer = stackalloc char[256]; int remaining = item.Length; @@ -73,7 +73,7 @@ static void EncodeToPipe(ref BufferWriter writer, HtmlEncoder htmlEncoder, } } - internal struct BufferWriter : IBufferWriter + internal class BufferWriter : IBufferWriter { private readonly IBufferWriter _inner; private Memory _memory; From 14a288b4e7e5ba50843023594867d201eb304ae8 Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 14:07:47 -0700 Subject: [PATCH 07/10] no advance --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 0871258ee..f52415118 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Collections.Generic; using System.Globalization; -using System.IO.Pipelines; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -57,6 +56,8 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont Encoding.UTF8.GetBytes("", writer); + writer.Commit(); + await httpContext.Response.BodyWriter.FlushAsync(); static void EncodeToPipe(BufferWriter writer, HtmlEncoder htmlEncoder, string item) @@ -77,6 +78,7 @@ internal class BufferWriter : IBufferWriter { private readonly IBufferWriter _inner; private Memory _memory; + private int _buffered; public BufferWriter(IBufferWriter writer) { @@ -86,13 +88,22 @@ public BufferWriter(IBufferWriter writer) public void Advance(int count) { _memory = _memory.Slice(count); - _inner.Advance(count); + _buffered += count; + } + + public void Commit() + { + _inner.Advance(_buffered); + _buffered = 0; + _memory = default; } public Memory GetMemory(int sizeHint = 0) { if (_memory.Length == 0 || _memory.Length < sizeHint) { + _inner.Advance(_buffered); + _buffered = 0; _memory = _inner.GetMemory(sizeHint); } return _memory; From a4c8bb152c1da1364dae38c45ec60ce70b0914eb Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 14:18:21 -0700 Subject: [PATCH 08/10] no 0 --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index f52415118..b5d1f0774 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Benchmarks.Data; using Microsoft.AspNetCore.Http; +using static Azure.Core.HttpHeader; namespace Benchmarks.Middleware { @@ -93,17 +94,19 @@ public void Advance(int count) public void Commit() { - _inner.Advance(_buffered); - _buffered = 0; - _memory = default; + if (_buffered != 0) + { + _inner.Advance(_buffered); + _buffered = 0; + _memory = default; + } } public Memory GetMemory(int sizeHint = 0) { if (_memory.Length == 0 || _memory.Length < sizeHint) { - _inner.Advance(_buffered); - _buffered = 0; + Commit(); _memory = _inner.GetMemory(sizeHint); } return _memory; From 804f331c7c71abc6649c0430143dde4ac9e3fdde Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 14:47:32 -0700 Subject: [PATCH 09/10] u8 --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index b5d1f0774..29169649d 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -42,20 +42,18 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont var writer = new BufferWriter(httpContext.Response.BodyWriter); - Encoding.UTF8.GetBytes( - "Fortunes", - writer); + writer.Write("Fortunes
idmessage
"u8); foreach (var item in model) { - Encoding.UTF8.GetBytes("", writer); + writer.Write(""u8); } - Encoding.UTF8.GetBytes("
idmessage
", writer); + writer.Write("
"u8); Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), writer); - Encoding.UTF8.GetBytes("", writer); + writer.Write(""u8); EncodeToPipe(writer, htmlEncoder, item.Message); - Encoding.UTF8.GetBytes("
", writer); + writer.Write(""u8); writer.Commit(); From b1946366a5f2520d9b499a3799f584300cb0d02d Mon Sep 17 00:00:00 2001 From: Brennan Date: Wed, 23 Oct 2024 14:59:29 -0700 Subject: [PATCH 10/10] write int directly --- src/Benchmarks/Middleware/MiddlewareHelpers.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 29169649d..d1a31c291 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -4,13 +4,13 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Benchmarks.Data; using Microsoft.AspNetCore.Http; -using static Azure.Core.HttpHeader; namespace Benchmarks.Middleware { @@ -47,7 +47,13 @@ public static async Task RenderFortunesHtml(IEnumerable model, HttpCont foreach (var item in model) { writer.Write(""u8); - Encoding.UTF8.GetBytes(item.Id.ToString(CultureInfo.InvariantCulture), writer); + + const int maxFormatInt32Length = 10; + var span = writer.GetSpan(maxFormatInt32Length); + var res = item.Id.TryFormat(span, out var written, provider: CultureInfo.InvariantCulture); + Debug.Assert(res); + writer.Advance(written); + writer.Write(""u8); EncodeToPipe(writer, htmlEncoder, item.Message); writer.Write(""u8);