diff --git a/src/Benchmarks/Middleware/MiddlewareHelpers.cs b/src/Benchmarks/Middleware/MiddlewareHelpers.cs index 4aff93401..d1a31c291 100644 --- a/src/Benchmarks/Middleware/MiddlewareHelpers.cs +++ b/src/Benchmarks/Middleware/MiddlewareHelpers.cs @@ -1,7 +1,10 @@ // 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.Diagnostics; using System.Globalization; using System.Text; using System.Text.Encodings.Web; @@ -30,27 +33,92 @@ 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"); + await httpContext.Response.StartAsync(); + + var writer = new BufferWriter(httpContext.Response.BodyWriter); + + writer.Write("Fortunes
idmessage
"u8); + foreach (var item in model) { - sb.Append(""); + writer.Write(""u8); + } + + writer.Write("
idmessage
"); - sb.Append(item.Id.ToString(CultureInfo.InvariantCulture)); - sb.Append(""); - sb.Append(htmlEncoder.Encode(item.Message)); - sb.Append("
"u8); + + 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); + + writer.Commit(); + + await httpContext.Response.BodyWriter.FlushAsync(); + + static void EncodeToPipe(BufferWriter writer, 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; + Encoding.UTF8.GetBytes(buffer.Slice(0, written), writer); + } while (remaining != 0); + } + } + } + + internal class BufferWriter : IBufferWriter + { + private readonly IBufferWriter _inner; + private Memory _memory; + private int _buffered; + + public BufferWriter(IBufferWriter writer) + { + _inner = writer; + } + + public void Advance(int count) + { + _memory = _memory.Slice(count); + _buffered += count; + } + + public void Commit() + { + if (_buffered != 0) + { + _inner.Advance(_buffered); + _buffered = 0; + _memory = default; + } + } + + public Memory GetMemory(int sizeHint = 0) + { + if (_memory.Length == 0 || _memory.Length < sizeHint) + { + Commit(); + _memory = _inner.GetMemory(sizeHint); } + return _memory; + } - sb.Append(""); - 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); + public Span GetSpan(int sizeHint = 0) + { + return GetMemory(sizeHint).Span; } } }