diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index 7ab17d49f..e1901717c 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -1,7 +1,9 @@ // 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.Collections.Generic; +using System.Diagnostics; using System.IO.Pipelines; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -43,6 +45,37 @@ private void OutputFortunes(PipeWriter pipeWriter, List model) // Date header writer.Write(DateHeader.HeaderBytes); + var bodyStart = writer.Buffered; + // Body + writer.Write(_fortunesTableStart); + foreach (var item in model) + { + writer.Write(_fortunesRowStart); + writer.WriteNumeric((uint)item.Id); + writer.Write(_fortunesColumn); + HtmlEncoder.EncodeUtf8(item.Message.AsSpan(), writer.Span, out var bytesConsumed, out var bytesWritten, isFinalBlock: true); + Debug.Assert(bytesConsumed == item.Message.Length, "Not enough remaining space in the buffer"); + writer.Advance(bytesWritten); + writer.Write(_fortunesRowEnd); + } + writer.Write(_fortunesTableEnd); + lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart)); + + writer.Commit(); + } + + private void OutputFortunes(PipeWriter pipeWriter, List model) + { + var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361 + + writer.Write(_fortunesPreamble); + + var lengthWriter = writer; + writer.Write(_contentLengthGap); + + // Date header + writer.Write(DateHeader.HeaderBytes); + var bodyStart = writer.Buffered; // Body writer.Write(_fortunesTableStart); diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BufferWriter.cs index 348d812ff..36ad46928 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BufferWriter.cs @@ -45,7 +45,7 @@ public void Advance(int count) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ReadOnlySpan source) + public void Write(scoped ReadOnlySpan source) { if (_span.Length >= source.Length) { @@ -78,7 +78,7 @@ private void EnsureMore(int count = 0) _span = _output.GetSpan(count); } - private void WriteMultiBuffer(ReadOnlySpan source) + private void WriteMultiBuffer(scoped ReadOnlySpan source) { while (source.Length > 0) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/DapperDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/DapperDb.cs index e4e92c0c6..b610c1a39 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/DapperDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/DapperDb.cs @@ -13,20 +13,20 @@ public class DapperDb public DapperDb(AppSettings appSettings) => _connectionString = appSettings.ConnectionString; - public async Task> LoadFortunesRows() + public async Task> LoadFortunesRows() { - List result; + List result; using (var db = new NpgsqlConnection(_connectionString)) { // Note: don't need to open connection if only doing one thing; let dapper do it - result = (await db.QueryAsync("SELECT id, message FROM fortune")).AsList(); + result = (await db.QueryAsync("SELECT id, message FROM fortune")).AsList(); } - result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." )); + result.Add(new FortuneDapper(id: 0, message: "Additional fortune added at request time." )); result.Sort(); return result; } } -} \ No newline at end of file +} diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs index 83287dcbd..5a2d7c5a3 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/Fortune.cs @@ -1,5 +1,5 @@ -// 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. +// 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; @@ -7,7 +7,7 @@ namespace PlatformBenchmarks { public readonly struct Fortune : IComparable, IComparable { - public Fortune(int id, string message) + public Fortune(int id, byte[] message) { Id = id; Message = message; @@ -15,11 +15,11 @@ public Fortune(int id, string message) public int Id { get; } - public string Message { get; } + public byte[] Message { get; } public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used"); // Performance critical, using culture insensitive comparison - public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message); + public int CompareTo(Fortune other) => Message.AsSpan().SequenceCompareTo(other.Message.AsSpan()); } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneDapper.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneDapper.cs new file mode 100644 index 000000000..a24050681 --- /dev/null +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneDapper.cs @@ -0,0 +1,25 @@ +// 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; + +namespace PlatformBenchmarks +{ + public readonly struct FortuneDapper : IComparable, IComparable + { + public FortuneDapper(int id, string message) + { + Id = id; + Message = message; + } + + public int Id { get; } + + public string Message { get; } + + public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used"); + + // Performance critical, using culture insensitive comparison + public int CompareTo(FortuneDapper other) => string.CompareOrdinal(Message, other.Message); + } +} diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneEf.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneEf.cs index 80995b956..c9466c892 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneEf.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneEf.cs @@ -17,14 +17,7 @@ public class FortuneEf : IComparable, IComparable [Required] public string Message { get; set; } - public int CompareTo(object obj) - { - return CompareTo((FortuneEf)obj); - } - - public int CompareTo(FortuneEf other) - { - return String.CompareOrdinal(Message, other.Message); - } + public int CompareTo(object obj) => CompareTo((FortuneEf)obj); + public int CompareTo(FortuneEf other) => String.CompareOrdinal(Message, other.Message); } -} \ No newline at end of file +} diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs index c298062a8..55132e74b 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs @@ -255,17 +255,19 @@ public async Task> LoadFortunesRows() result.Add(new Fortune ( id: rdr.GetInt32(0), - message: rdr.GetString(1) + message: rdr.GetFieldValue(1) )); } } - result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." )); + result.Add(new Fortune(id: 0, AdditionalFortune)); result.Sort(); return result; } + private readonly byte[] AdditionalFortune = "Additional fortune added at request time."u8.ToArray(); + private (NpgsqlCommand readCmd, NpgsqlParameter idParameter) CreateReadCommand(NpgsqlConnection connection) { #if NET6_0_OR_GREATER