diff --git a/samples/LowAllocationWebServer/Shared/SampleServer.cs b/samples/LowAllocationWebServer/Shared/SampleServer.cs index 0c47539fa99..d643864a67f 100644 --- a/samples/LowAllocationWebServer/Shared/SampleServer.cs +++ b/samples/LowAllocationWebServer/Shared/SampleServer.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Text.Http.Parser; using Microsoft.Net; +using System.Buffers.Writer; namespace LowAllocationWebServer { @@ -62,7 +63,7 @@ static void WriteResponseForGetJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriter(response, true, prettyPrint: false); + var jsonWriter = new JsonWriter(new BufferWriter>(response), true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < 5; i++) @@ -94,7 +95,7 @@ static void WriteResponseForPostJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriter(response, true, prettyPrint: false); + var jsonWriter = new JsonWriter(new BufferWriter>(response), true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < requestedCount; i++) diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs index 4b17d21f94e..0a73b0de2f0 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs @@ -56,12 +56,7 @@ public void Ensure(int count = 1) [MethodImpl(MethodImplOptions.NoInlining)] private void EnsureMore(int count = 0) { - var buffered = _buffered; - if (buffered > 0) - { - _buffered = 0; - _output.Advance(buffered); - } + Flush(); _span = _output.GetSpan(count); } diff --git a/src/System.Text.JsonLab/System.Text.JsonLab.csproj b/src/System.Text.JsonLab/System.Text.JsonLab.csproj index 1061cc23805..e70caf4e5c0 100644 --- a/src/System.Text.JsonLab/System.Text.JsonLab.csproj +++ b/src/System.Text.JsonLab/System.Text.JsonLab.csproj @@ -13,5 +13,6 @@ + diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 07dba35814c..2dfaf973fd9 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -3,30 +3,31 @@ using System.Buffers; using System.Buffers.Text; +using System.Buffers.Writer; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System.Text.JsonLab { - public struct JsonWriter + public ref struct JsonWriter { private static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); private static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); - readonly bool _prettyPrint; - readonly IBufferWriter _bufferWriter; - readonly bool _isUtf8; + private readonly bool _prettyPrint; + private BufferWriter> _bufferWriter; + private readonly bool _isUtf8; - int _indent; - bool _firstItem; + private int _indent; + private bool _firstItem; /// /// Constructs a JSON writer with a specified . /// /// An instance of used for writing bytes to an output channel. /// Specifies whether to add whitespace to the output text for user readability. - public JsonWriter(IBufferWriter bufferWriter, bool isUtf8, bool prettyPrint = false) + public JsonWriter(BufferWriter> bufferWriter, bool isUtf8, bool prettyPrint = false) { _bufferWriter = bufferWriter; _prettyPrint = prettyPrint; @@ -1077,14 +1078,18 @@ public void WriteNull() } } + public void Flush() => _bufferWriter.Flush(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteNumberUtf8(long value) { //TODO: Optimize, this is too slow - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1093,10 +1098,12 @@ private void WriteNumberUtf8(long value) private void WriteNumberUtf16(long value) { //TODO: Optimize, this is too slow - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1104,10 +1111,12 @@ private void WriteNumberUtf16(long value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteNumber(ulong value) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1115,10 +1124,12 @@ private void WriteNumber(ulong value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteDateTime(DateTime value) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1126,10 +1137,12 @@ private void WriteDateTime(DateTime value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteDateTimeOffset(DateTimeOffset value) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1137,10 +1150,12 @@ private void WriteDateTimeOffset(DateTimeOffset value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteGuid(Guid value) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf8)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1148,10 +1163,12 @@ private void WriteGuid(Guid value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteJsonValueUtf8(ReadOnlySpan values) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!SymbolTable.InvariantUtf8.TryEncode(values, buffer, out int consumed, out written)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1159,10 +1176,12 @@ private void WriteJsonValueUtf8(ReadOnlySpan values) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteJsonValueUtf16(ReadOnlySpan values) { - var buffer = _bufferWriter.GetSpan(); + Span buffer = _bufferWriter.Buffer; int written; while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) + { buffer = EnsureBuffer(); + } _bufferWriter.Advance(written); } @@ -1170,14 +1189,15 @@ private void WriteJsonValueUtf16(ReadOnlySpan values) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf8(byte value) { - MemoryMarshal.GetReference(EnsureBuffer(1)) = value; + Span buffer = EnsureBuffer(1); + MemoryMarshal.GetReference(buffer) = value; _bufferWriter.Advance(1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf16(byte value) { - var buffer = EnsureBuffer(2); + Span buffer = EnsureBuffer(2); Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; _bufferWriter.Advance(2); } @@ -1210,7 +1230,7 @@ private void WriteSpacingUtf8(bool newline = true) var bytesNeeded = newline ? 2 : 0; bytesNeeded += (indent + 1) * 2; - var buffer = EnsureBuffer(bytesNeeded); + Span buffer = EnsureBuffer(bytesNeeded); ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer); int idx = 0; @@ -1240,7 +1260,7 @@ private void WriteSpacingUtf16(bool newline = true) bytesNeeded += (indent + 1) * 2; bytesNeeded *= sizeof(char); - var buffer = EnsureBuffer(bytesNeeded); + Span buffer = EnsureBuffer(bytesNeeded); var span = MemoryMarshal.Cast(buffer); ref char utf16Bytes = ref MemoryMarshal.GetReference(span); int idx = 0; @@ -1263,10 +1283,10 @@ private void WriteSpacingUtf16(bool newline = true) [MethodImpl(MethodImplOptions.AggressiveInlining)] private Span EnsureBuffer(int needed = 256) { - Span buffer = _bufferWriter.GetSpan(needed); + _bufferWriter.Ensure(needed); + Span buffer = _bufferWriter.Buffer; if (buffer.Length < needed) JsonThrowHelper.ThrowOutOfMemoryException(); - return buffer; } diff --git a/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index a2eeb4def15..2e2230034a3 100644 --- a/tests/Benchmarks/System.IO.Pipelines/E2E.cs +++ b/tests/Benchmarks/System.IO.Pipelines/E2E.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Buffers.Text; +using System.Buffers.Writer; using System.IO.Pipelines.Samples; using System.Text; using System.Text.Formatting; @@ -29,7 +31,8 @@ public class E2E [Arguments(10000, 256)] public void TechEmpowerHelloWorldNoIO(int numberOfRequests, int concurrentConnections) { - RawInMemoryHttpServer.Run(numberOfRequests, concurrentConnections, s_genericRequest, (request, response) => { + RawInMemoryHttpServer.Run(numberOfRequests, concurrentConnections, s_genericRequest, (request, response) => + { var formatter = new BufferWriterFormatter(response, SymbolTable.InvariantUtf8); formatter.Append("HTTP/1.1 200 OK"); formatter.Append("\r\nContent-Length: 13"); @@ -47,7 +50,8 @@ public void TechEmpowerHelloWorldNoIO(int numberOfRequests, int concurrentConnec [Arguments(10000, 256)] public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) { - RawInMemoryHttpServer.Run(numberOfRequests, concurrentConnections, s_genericRequest, (request, response) => { + RawInMemoryHttpServer.Run(numberOfRequests, concurrentConnections, s_genericRequest, (request, response) => + { var formatter = new BufferWriterFormatter(response, SymbolTable.InvariantUtf8); formatter.Append("HTTP/1.1 200 OK"); formatter.Append("\r\nContent-Length: 25"); @@ -57,7 +61,7 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Append("\r\n\r\n"); // write body - var writer = new JsonWriter(formatter, true); + var writer = new JsonWriter(new BufferWriter>(formatter), true, true); writer.WriteObjectStart(); writer.WriteAttribute("message", "Hello, World!"); writer.WriteObjectEnd(); diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index c7ea17a76e0..d42b232cde4 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using BenchmarkDotNet.Attributes; +using System.Buffers; using System.Buffers.Text; +using System.Buffers.Writer; using System.IO; using System.Text.Formatting; @@ -56,10 +58,11 @@ public void Setup() public void WriterSystemTextJsonBasic() { _arrayFormatter.Clear(); + var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonBasicUtf8(Formatted, _arrayFormatter, _data); + WriterSystemTextJsonBasicUtf8(Formatted, bufferWriter, _data); else - WriterSystemTextJsonBasicUtf16(Formatted, _arrayFormatter, _data); + WriterSystemTextJsonBasicUtf16(Formatted, bufferWriter, _data); } [Benchmark] @@ -72,10 +75,11 @@ public void WriterNewtonsoftBasic() public void WriterSystemTextJsonHelloWorld() { _arrayFormatter.Clear(); + var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonHelloWorldUtf8(Formatted, _arrayFormatter); + WriterSystemTextJsonHelloWorldUtf8(Formatted, bufferWriter); else - WriterSystemTextJsonHelloWorldUtf16(Formatted, _arrayFormatter); + WriterSystemTextJsonHelloWorldUtf16(Formatted, bufferWriter); } [Benchmark] @@ -100,7 +104,7 @@ private TextWriter GetWriter() return writer; } - private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter output, int[] data) + private static void WriterSystemTextJsonBasicUtf8(bool formatted, BufferWriter> output, int[] data) { var json = new JsonWriter(output, true, formatted); @@ -127,9 +131,10 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter json.WriteArrayEnd(); json.WriteObjectEnd(); + json.Flush(); } - private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatter output, int[] data) + private static void WriterSystemTextJsonBasicUtf16(bool formatted, BufferWriter> output, int[] data) { var json = new JsonWriter(output, false, formatted); @@ -156,6 +161,7 @@ private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatte json.WriteArrayEnd(); json.WriteObjectEnd(); + json.Flush(); } private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int[] data) @@ -199,22 +205,24 @@ private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int } } - private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayFormatter output) + private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, BufferWriter> output) { var json = new JsonWriter(output, true, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); json.WriteObjectEnd(); + json.Flush(); } - private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) + private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, BufferWriter> output) { var json = new JsonWriter(output, false, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); json.WriteObjectEnd(); + json.Flush(); } private static void WriterNewtonsoftHelloWorld(bool formatted, TextWriter writer) diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index 55015a9a659..6cc6e38527d 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -6,6 +6,8 @@ using System.Text.Formatting; using System.Buffers.Text; using System.IO; +using System.Buffers.Writer; +using System.Buffers; namespace System.Text.JsonLab.Tests { @@ -17,7 +19,8 @@ public class JsonWriterTests public void WriteJsonUtf8() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var json = new JsonWriter(formatter, true, prettyPrint: false); + var bufferWriter = new BufferWriter>(formatter); + var json = new JsonWriter(bufferWriter, true, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -25,7 +28,8 @@ public void WriteJsonUtf8() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = new JsonWriter(formatter, true, prettyPrint: true); + bufferWriter = new BufferWriter>(formatter); + json = new JsonWriter(bufferWriter, true, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -37,7 +41,8 @@ public void WriteJsonUtf8() public void WriteJsonUtf16() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var json = new JsonWriter(formatter, false, prettyPrint: false); + var bufferWriter = new BufferWriter>(formatter); + var json = new JsonWriter(bufferWriter, false, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -45,7 +50,8 @@ public void WriteJsonUtf16() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = new JsonWriter(formatter, false, prettyPrint: true); + bufferWriter = new BufferWriter>(formatter); + json = new JsonWriter(bufferWriter, false, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -76,6 +82,7 @@ static void Write(ref JsonWriter json) json.WriteValue(425123); json.WriteArrayEnd(); json.WriteObjectEnd(); + json.Flush(); } [Theory] @@ -86,11 +93,13 @@ public void WriteHelloWorldJsonUtf16(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: false); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var jsonUtf16 = new JsonWriter(output, false, prettyPrint); + var bufferWriter = new BufferWriter>(output); + var jsonUtf16 = new JsonWriter(bufferWriter, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("message", "Hello, World!"); jsonUtf16.WriteObjectEnd(); + jsonUtf16.Flush(); ArraySegment formatted = output.Formatted; string actualStr = Encoding.Unicode.GetString(formatted.Array, formatted.Offset, formatted.Count); @@ -106,11 +115,13 @@ public void WriteHelloWorldJsonUtf8(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: true); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var jsonUtf8 = new JsonWriter(output, true, prettyPrint); + var bufferWriter = new BufferWriter>(output); + var jsonUtf8 = new JsonWriter(bufferWriter, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("message", "Hello, World!"); jsonUtf8.WriteObjectEnd(); + jsonUtf8.Flush(); ArraySegment formatted = output.Formatted; string actualStr = Encoding.UTF8.GetString(formatted.Array, formatted.Offset, formatted.Count); @@ -128,7 +139,8 @@ public void WriteBasicJsonUtf16(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: false, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var jsonUtf16 = new JsonWriter(output, false, prettyPrint); + var bufferWriter = new BufferWriter>(output); + var jsonUtf16 = new JsonWriter(bufferWriter, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("age", 42); @@ -154,6 +166,8 @@ public void WriteBasicJsonUtf16(bool prettyPrint) jsonUtf16.WriteObjectEnd(); + jsonUtf16.Flush(); + ArraySegment formatted = output.Formatted; string actualStr = Encoding.Unicode.GetString(formatted.Array, formatted.Offset, formatted.Count); @@ -170,7 +184,8 @@ public void WriteBasicJsonUtf8(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: true, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var jsonUtf8 = new JsonWriter(output, true, prettyPrint); + var bufferWriter = new BufferWriter>(output); + var jsonUtf8 = new JsonWriter(bufferWriter, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("age", 42); @@ -196,6 +211,8 @@ public void WriteBasicJsonUtf8(bool prettyPrint) jsonUtf8.WriteObjectEnd(); + jsonUtf8.Flush(); + ArraySegment formatted = output.Formatted; string actualStr = Encoding.UTF8.GetString(formatted.Array, formatted.Offset, formatted.Count);