From 9079487b90d6c0eac2f4ec016a7525196a529354 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Fri, 8 Jun 2018 16:47:10 -0700 Subject: [PATCH 01/17] Add support for BufferWriter to the JsonWriter --- .../System.Text.JsonLab.csproj | 1 + .../System/Text/Json/JsonWriter.cs | 163 +++++++++++++----- .../JsonWriterTests.cs | 26 ++- 3 files changed, 137 insertions(+), 53 deletions(-) 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..4c8674be43c 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; @@ -58,7 +59,8 @@ public void WriteObjectStart() private void WriteStartUtf8(int bytesNeeded, byte token) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -77,11 +79,13 @@ private void WriteStartUtf8(int bytesNeeded, byte token) byteBuffer[idx] = token; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } private void WriteStartUtf16(int bytesNeeded, byte token) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -100,6 +104,7 @@ private void WriteStartUtf16(int bytesNeeded, byte token) charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } /// @@ -129,7 +134,8 @@ public void WriteObjectStart(string name) private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, byte token) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -159,12 +165,14 @@ private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, by byteBuffer[idx++] = token; _bufferWriter.Advance(idx); + _bufferWriter.Flush(); _firstItem = false; } private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -189,6 +197,7 @@ private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, b charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); _firstItem = false; } @@ -211,7 +220,8 @@ public void WriteObjectEnd() private void WriteEndUtf8(int bytesNeeded, byte token) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (_prettyPrint) @@ -219,11 +229,13 @@ private void WriteEndUtf8(int bytesNeeded, byte token) byteBuffer[idx] = token; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } private void WriteEndUtf16(int bytesNeeded, byte token) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (_prettyPrint) @@ -231,6 +243,7 @@ private void WriteEndUtf16(int bytesNeeded, byte token) charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } /// @@ -321,7 +334,8 @@ public void WriteAttribute(string name, string value) private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan valueSpanByte, int bytesNeeded) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -361,12 +375,14 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -395,6 +411,7 @@ private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, ReadOnlySpan nameSpanByte, int bytesNeeded) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -594,12 +612,14 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded byteBuffer[idx++] = JsonConstants.Space; _bufferWriter.Advance(idx); + _bufferWriter.Flush(); _firstItem = false; } private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -622,6 +642,7 @@ private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeede charBuffer[idx] = (char)JsonConstants.Space; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); _firstItem = false; } @@ -647,7 +668,8 @@ public void WriteValue(string value) private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) { - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -671,12 +693,14 @@ private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) byteBuffer[idx++] = JsonConstants.Quote; _bufferWriter.Advance(idx); + _bufferWriter.Flush(); _firstItem = false; } private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -694,6 +718,7 @@ private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) charBuffer[idx] = (char)JsonConstants.Quote; _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); _firstItem = false; } @@ -725,7 +750,8 @@ private void WriteValueUtf8(long value, int bytesNeeded) int digitCount = CountDigits((ulong)value); bytesNeeded += sizeof(byte) * digitCount; - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (!_firstItem) @@ -741,6 +767,7 @@ private void WriteValueUtf8(long value, int bytesNeeded) WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } private void WriteValueUtf16(long value, int bytesNeeded) @@ -755,7 +782,8 @@ private void WriteValueUtf16(long value, int bytesNeeded) int digitCount = CountDigits((ulong)value); bytesNeeded += sizeof(char) * digitCount; - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); int idx = 0; if (!_firstItem) @@ -771,6 +799,7 @@ private void WriteValueUtf16(long value, int bytesNeeded) WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1011,7 +1040,8 @@ public void WriteNull() if (_isUtf8) { int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; - Span byteBuffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span byteBuffer = _bufferWriter.Buffer; int idx = 0; if (_firstItem) { @@ -1039,6 +1069,7 @@ public void WriteNull() JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } else { @@ -1074,6 +1105,7 @@ public void WriteNull() nullLiteral.CopyTo(byteBuffer.Slice(idx * 2)); _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } } @@ -1081,105 +1113,141 @@ public void WriteNull() 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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [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(); + { + EnsureBuffer(); + buffer = _bufferWriter.Buffer; + } _bufferWriter.Advance(written); + _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf8(byte value) { - MemoryMarshal.GetReference(EnsureBuffer(1)) = value; + EnsureBuffer(1); + MemoryMarshal.GetReference(_bufferWriter.Buffer) = value; _bufferWriter.Advance(1); + _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf16(byte value) { - var buffer = EnsureBuffer(2); + EnsureBuffer(2); + Span buffer = _bufferWriter.Buffer; Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; _bufferWriter.Advance(2); + _bufferWriter.Flush(); } // TODO: Once public methods are optimized, remove this. @@ -1210,7 +1278,8 @@ private void WriteSpacingUtf8(bool newline = true) var bytesNeeded = newline ? 2 : 0; bytesNeeded += (indent + 1) * 2; - var buffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span buffer = _bufferWriter.Buffer; ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer); int idx = 0; @@ -1227,6 +1296,7 @@ private void WriteSpacingUtf8(bool newline = true) } _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } // TODO: Once public methods are optimized, remove this. @@ -1240,7 +1310,8 @@ private void WriteSpacingUtf16(bool newline = true) bytesNeeded += (indent + 1) * 2; bytesNeeded *= sizeof(char); - var buffer = EnsureBuffer(bytesNeeded); + EnsureBuffer(bytesNeeded); + Span buffer = _bufferWriter.Buffer; var span = MemoryMarshal.Cast(buffer); ref char utf16Bytes = ref MemoryMarshal.GetReference(span); int idx = 0; @@ -1258,16 +1329,18 @@ private void WriteSpacingUtf16(bool newline = true) } _bufferWriter.Advance(bytesNeeded); + _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Span EnsureBuffer(int needed = 256) + private void EnsureBuffer(int needed = 256) { - Span buffer = _bufferWriter.GetSpan(needed); + _bufferWriter.Ensure(needed); + /*Span buffer = _bufferWriter.GetSpan(needed); if (buffer.Length < needed) JsonThrowHelper.ThrowOutOfMemoryException(); - return buffer; + return buffer;*/ } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index 55015a9a659..196e0bfdd97 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; @@ -86,7 +92,8 @@ 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!"); @@ -106,7 +113,8 @@ 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!"); @@ -128,7 +136,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); @@ -170,7 +179,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); From cc02f6724ca2a35631f9575d683dd1a8aae2b1c2 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Mon, 11 Jun 2018 18:54:42 -0700 Subject: [PATCH 02/17] Fix uses of JsonWriter in perf tests and samples --- .../Shared/SampleServer.cs | 5 +++-- tests/Benchmarks/System.IO.Pipelines/E2E.cs | 6 ++++-- .../System.Text.JsonLab/JsonWriterPerf.cs | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) 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/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index a2eeb4def15..81e36d436bd 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; @@ -55,9 +57,9 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Format("\r\nDate: {0:R}", DateTime.UtcNow); formatter.Append("Server: System.IO.Pipelines"); 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..4e6dc38bc00 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; @@ -57,9 +59,9 @@ public void WriterSystemTextJsonBasic() { _arrayFormatter.Clear(); if (IsUTF8Encoded) - WriterSystemTextJsonBasicUtf8(Formatted, _arrayFormatter, _data); + WriterSystemTextJsonBasicUtf8(Formatted, new BufferWriter>(_arrayFormatter), _data); else - WriterSystemTextJsonBasicUtf16(Formatted, _arrayFormatter, _data); + WriterSystemTextJsonBasicUtf16(Formatted, new BufferWriter>(_arrayFormatter), _data); } [Benchmark] @@ -73,9 +75,9 @@ public void WriterSystemTextJsonHelloWorld() { _arrayFormatter.Clear(); if (IsUTF8Encoded) - WriterSystemTextJsonHelloWorldUtf8(Formatted, _arrayFormatter); + WriterSystemTextJsonHelloWorldUtf8(Formatted, new BufferWriter>(_arrayFormatter)); else - WriterSystemTextJsonHelloWorldUtf16(Formatted, _arrayFormatter); + WriterSystemTextJsonHelloWorldUtf16(Formatted, new BufferWriter>(_arrayFormatter)); } [Benchmark] @@ -100,7 +102,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); @@ -129,7 +131,7 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter json.WriteObjectEnd(); } - 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); @@ -199,7 +201,7 @@ 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); @@ -208,7 +210,7 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayForm json.WriteObjectEnd(); } - private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) + private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, BufferWriter> output) { var json = new JsonWriter(output, false, formatted); From b5a0e9a1f66b0299cb44ee9bae5de5452fa96c8e Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Mon, 11 Jun 2018 19:36:41 -0700 Subject: [PATCH 03/17] Code clean up and change EnsureBuffer to return the span. --- .../System/Text/Json/JsonWriter.cs | 90 +++++++------------ tests/Benchmarks/System.IO.Pipelines/E2E.cs | 8 +- 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 4c8674be43c..0dcfa249f76 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -59,8 +59,7 @@ public void WriteObjectStart() private void WriteStartUtf8(int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -84,8 +83,7 @@ private void WriteStartUtf8(int bytesNeeded, byte token) private void WriteStartUtf16(int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -134,8 +132,7 @@ public void WriteObjectStart(string name) private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -171,8 +168,7 @@ private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, by private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -220,8 +216,7 @@ public void WriteObjectEnd() private void WriteEndUtf8(int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (_prettyPrint) @@ -234,8 +229,7 @@ private void WriteEndUtf8(int bytesNeeded, byte token) private void WriteEndUtf16(int bytesNeeded, byte token) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (_prettyPrint) @@ -334,8 +328,7 @@ public void WriteAttribute(string name, string value) private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan valueSpanByte, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -381,8 +374,7 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -583,8 +575,7 @@ public void WriteAttributeNull(string name) private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -618,8 +609,7 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -668,8 +658,7 @@ public void WriteValue(string value) private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -699,8 +688,7 @@ private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) { - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -750,8 +738,7 @@ private void WriteValueUtf8(long value, int bytesNeeded) int digitCount = CountDigits((ulong)value); bytesNeeded += sizeof(byte) * digitCount; - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (!_firstItem) @@ -782,8 +769,8 @@ private void WriteValueUtf16(long value, int bytesNeeded) int digitCount = CountDigits((ulong)value); bytesNeeded += sizeof(char) * digitCount; - EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(_bufferWriter.Buffer); + + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; if (!_firstItem) @@ -1040,8 +1027,7 @@ public void WriteNull() if (_isUtf8) { int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; - EnsureBuffer(bytesNeeded); - Span byteBuffer = _bufferWriter.Buffer; + Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; if (_firstItem) { @@ -1117,8 +1103,7 @@ private void WriteNumberUtf8(long value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1133,8 +1118,7 @@ private void WriteNumberUtf16(long value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1148,8 +1132,7 @@ private void WriteNumber(ulong value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1163,8 +1146,7 @@ private void WriteDateTime(DateTime value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1178,8 +1160,7 @@ private void WriteDateTimeOffset(DateTimeOffset value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1193,8 +1174,7 @@ private void WriteGuid(Guid value) int written; while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf8)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1208,8 +1188,7 @@ private void WriteJsonValueUtf8(ReadOnlySpan values) int written; while (!SymbolTable.InvariantUtf8.TryEncode(values, buffer, out int consumed, out written)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1223,8 +1202,7 @@ private void WriteJsonValueUtf16(ReadOnlySpan values) int written; while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) { - EnsureBuffer(); - buffer = _bufferWriter.Buffer; + buffer = EnsureBuffer(); } _bufferWriter.Advance(written); @@ -1234,8 +1212,8 @@ private void WriteJsonValueUtf16(ReadOnlySpan values) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf8(byte value) { - EnsureBuffer(1); - MemoryMarshal.GetReference(_bufferWriter.Buffer) = value; + Span buffer = EnsureBuffer(1); + MemoryMarshal.GetReference(buffer) = value; _bufferWriter.Advance(1); _bufferWriter.Flush(); } @@ -1243,8 +1221,7 @@ private void WriteControlUtf8(byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteControlUtf16(byte value) { - EnsureBuffer(2); - Span buffer = _bufferWriter.Buffer; + Span buffer = EnsureBuffer(2); Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; _bufferWriter.Advance(2); _bufferWriter.Flush(); @@ -1278,8 +1255,7 @@ private void WriteSpacingUtf8(bool newline = true) var bytesNeeded = newline ? 2 : 0; bytesNeeded += (indent + 1) * 2; - EnsureBuffer(bytesNeeded); - Span buffer = _bufferWriter.Buffer; + Span buffer = EnsureBuffer(bytesNeeded); ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer); int idx = 0; @@ -1310,8 +1286,7 @@ private void WriteSpacingUtf16(bool newline = true) bytesNeeded += (indent + 1) * 2; bytesNeeded *= sizeof(char); - EnsureBuffer(bytesNeeded); - Span buffer = _bufferWriter.Buffer; + Span buffer = EnsureBuffer(bytesNeeded); var span = MemoryMarshal.Cast(buffer); ref char utf16Bytes = ref MemoryMarshal.GetReference(span); int idx = 0; @@ -1333,14 +1308,13 @@ private void WriteSpacingUtf16(bool newline = true) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureBuffer(int needed = 256) + private Span EnsureBuffer(int needed = 256) { _bufferWriter.Ensure(needed); - /*Span buffer = _bufferWriter.GetSpan(needed); + Span buffer = _bufferWriter.Buffer; if (buffer.Length < needed) JsonThrowHelper.ThrowOutOfMemoryException(); - - return buffer;*/ + return buffer; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index 81e36d436bd..2e2230034a3 100644 --- a/tests/Benchmarks/System.IO.Pipelines/E2E.cs +++ b/tests/Benchmarks/System.IO.Pipelines/E2E.cs @@ -31,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"); @@ -49,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 +59,7 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Format("\r\nDate: {0:R}", DateTime.UtcNow); formatter.Append("Server: System.IO.Pipelines"); formatter.Append("\r\n\r\n"); - + // write body var writer = new JsonWriter(new BufferWriter>(formatter), true, true); writer.WriteObjectStart(); From ca857421c9ee2d461f96b83bdde2a385ba3079e9 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Mon, 11 Jun 2018 19:39:55 -0700 Subject: [PATCH 04/17] Remove extra whitespace. --- src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 0dcfa249f76..0e122bbdc31 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -769,7 +769,6 @@ private void WriteValueUtf16(long value, int bytesNeeded) int digitCount = CountDigits((ulong)value); bytesNeeded += sizeof(char) * digitCount; - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; From 77e1e93444b66f603c7ea4a92c711ed843dd001d Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Tue, 12 Jun 2018 22:12:37 -0700 Subject: [PATCH 05/17] Remove chatty calls to Flush within the JsonWriter APIs --- .../System/Buffers/Writer/BufferWriterT.cs | 7 +---- .../System/Text/Json/JsonWriter.cs | 28 ------------------- .../System.Text.JsonLab/JsonWriterPerf.cs | 12 +++++--- 3 files changed, 9 insertions(+), 38 deletions(-) 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/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 0e122bbdc31..b9c6055509d 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -78,7 +78,6 @@ private void WriteStartUtf8(int bytesNeeded, byte token) byteBuffer[idx] = token; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } private void WriteStartUtf16(int bytesNeeded, byte token) @@ -102,7 +101,6 @@ private void WriteStartUtf16(int bytesNeeded, byte token) charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } /// @@ -162,7 +160,6 @@ private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, by byteBuffer[idx++] = token; _bufferWriter.Advance(idx); - _bufferWriter.Flush(); _firstItem = false; } @@ -193,7 +190,6 @@ private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, b charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); _firstItem = false; } @@ -224,7 +220,6 @@ private void WriteEndUtf8(int bytesNeeded, byte token) byteBuffer[idx] = token; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } private void WriteEndUtf16(int bytesNeeded, byte token) @@ -237,7 +232,6 @@ private void WriteEndUtf16(int bytesNeeded, byte token) charBuffer[idx] = (char)token; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } /// @@ -368,7 +362,6 @@ private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan nameSpanChar, ReadOnlySpan nameSpanByte, int bytesNeeded byteBuffer[idx++] = JsonConstants.Space; _bufferWriter.Advance(idx); - _bufferWriter.Flush(); _firstItem = false; } @@ -632,7 +623,6 @@ private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeede charBuffer[idx] = (char)JsonConstants.Space; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); _firstItem = false; } @@ -682,7 +672,6 @@ private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) byteBuffer[idx++] = JsonConstants.Quote; _bufferWriter.Advance(idx); - _bufferWriter.Flush(); _firstItem = false; } @@ -706,7 +695,6 @@ private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) charBuffer[idx] = (char)JsonConstants.Quote; _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); _firstItem = false; } @@ -754,7 +742,6 @@ private void WriteValueUtf8(long value, int bytesNeeded) WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } private void WriteValueUtf16(long value, int bytesNeeded) @@ -785,7 +772,6 @@ private void WriteValueUtf16(long value, int bytesNeeded) WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1054,7 +1040,6 @@ public void WriteNull() JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } else { @@ -1090,7 +1075,6 @@ public void WriteNull() nullLiteral.CopyTo(byteBuffer.Slice(idx * 2)); _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } } @@ -1106,7 +1090,6 @@ private void WriteNumberUtf8(long value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1121,7 +1104,6 @@ private void WriteNumberUtf16(long value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1135,7 +1117,6 @@ private void WriteNumber(ulong value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1149,7 +1130,6 @@ private void WriteDateTime(DateTime value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1163,7 +1143,6 @@ private void WriteDateTimeOffset(DateTimeOffset value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1177,7 +1156,6 @@ private void WriteGuid(Guid value) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1191,7 +1169,6 @@ private void WriteJsonValueUtf8(ReadOnlySpan values) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1205,7 +1182,6 @@ private void WriteJsonValueUtf16(ReadOnlySpan values) } _bufferWriter.Advance(written); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1214,7 +1190,6 @@ private void WriteControlUtf8(byte value) Span buffer = EnsureBuffer(1); MemoryMarshal.GetReference(buffer) = value; _bufferWriter.Advance(1); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1223,7 +1198,6 @@ private void WriteControlUtf16(byte value) Span buffer = EnsureBuffer(2); Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; _bufferWriter.Advance(2); - _bufferWriter.Flush(); } // TODO: Once public methods are optimized, remove this. @@ -1271,7 +1245,6 @@ private void WriteSpacingUtf8(bool newline = true) } _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } // TODO: Once public methods are optimized, remove this. @@ -1303,7 +1276,6 @@ private void WriteSpacingUtf16(bool newline = true) } _bufferWriter.Advance(bytesNeeded); - _bufferWriter.Flush(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index 4e6dc38bc00..64a7f1230ce 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -58,10 +58,12 @@ public void Setup() public void WriterSystemTextJsonBasic() { _arrayFormatter.Clear(); + var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonBasicUtf8(Formatted, new BufferWriter>(_arrayFormatter), _data); + WriterSystemTextJsonBasicUtf8(Formatted, bufferWriter, _data); else - WriterSystemTextJsonBasicUtf16(Formatted, new BufferWriter>(_arrayFormatter), _data); + WriterSystemTextJsonBasicUtf16(Formatted, bufferWriter, _data); + bufferWriter.Flush(); } [Benchmark] @@ -74,10 +76,12 @@ public void WriterNewtonsoftBasic() public void WriterSystemTextJsonHelloWorld() { _arrayFormatter.Clear(); + var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonHelloWorldUtf8(Formatted, new BufferWriter>(_arrayFormatter)); + WriterSystemTextJsonHelloWorldUtf8(Formatted, bufferWriter); else - WriterSystemTextJsonHelloWorldUtf16(Formatted, new BufferWriter>(_arrayFormatter)); + WriterSystemTextJsonHelloWorldUtf16(Formatted, bufferWriter); + bufferWriter.Flush(); } [Benchmark] From e3cf107d4a191e4ee5fd6507caf1b1bde6c957a2 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Tue, 12 Jun 2018 22:30:12 -0700 Subject: [PATCH 06/17] Add Flush API and fix tests. --- src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs | 2 ++ tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs | 6 ++++-- tests/System.Text.JsonLab.Tests/JsonWriterTests.cs | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index b9c6055509d..2dfaf973fd9 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -1078,6 +1078,8 @@ public void WriteNull() } } + public void Flush() => _bufferWriter.Flush(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteNumberUtf8(long value) { diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index 64a7f1230ce..d42b232cde4 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -63,7 +63,6 @@ public void WriterSystemTextJsonBasic() WriterSystemTextJsonBasicUtf8(Formatted, bufferWriter, _data); else WriterSystemTextJsonBasicUtf16(Formatted, bufferWriter, _data); - bufferWriter.Flush(); } [Benchmark] @@ -81,7 +80,6 @@ public void WriterSystemTextJsonHelloWorld() WriterSystemTextJsonHelloWorldUtf8(Formatted, bufferWriter); else WriterSystemTextJsonHelloWorldUtf16(Formatted, bufferWriter); - bufferWriter.Flush(); } [Benchmark] @@ -133,6 +131,7 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, BufferWriter> output, int[] data) @@ -162,6 +161,7 @@ private static void WriterSystemTextJsonBasicUtf16(bool formatted, BufferWriter< json.WriteArrayEnd(); json.WriteObjectEnd(); + json.Flush(); } private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int[] data) @@ -212,6 +212,7 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, BufferWri json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); json.WriteObjectEnd(); + json.Flush(); } private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, BufferWriter> output) @@ -221,6 +222,7 @@ private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, BufferWr 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 196e0bfdd97..6cc6e38527d 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -82,6 +82,7 @@ static void Write(ref JsonWriter json) json.WriteValue(425123); json.WriteArrayEnd(); json.WriteObjectEnd(); + json.Flush(); } [Theory] @@ -98,6 +99,7 @@ public void WriteHelloWorldJsonUtf16(bool 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); @@ -119,6 +121,7 @@ public void WriteHelloWorldJsonUtf8(bool 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); @@ -163,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); @@ -206,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); From 90fccdfcf661a004f8331e2cd29a38aa708eeafb Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Wed, 13 Jun 2018 00:03:50 -0700 Subject: [PATCH 07/17] Address PR feedback - make JsonWriter generic. --- .../Shared/SampleServer.cs | 5 ++-- .../System/Text/Json/JsonWriter.cs | 26 +++++++++++++++-- tests/Benchmarks/System.IO.Pipelines/E2E.cs | 4 +-- .../System.Text.JsonLab/JsonWriterPerf.cs | 28 ++++++++----------- .../JsonWriterTests.cs | 28 ++++++------------- 5 files changed, 47 insertions(+), 44 deletions(-) diff --git a/samples/LowAllocationWebServer/Shared/SampleServer.cs b/samples/LowAllocationWebServer/Shared/SampleServer.cs index d643864a67f..346a9bda818 100644 --- a/samples/LowAllocationWebServer/Shared/SampleServer.cs +++ b/samples/LowAllocationWebServer/Shared/SampleServer.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Text.Http.Parser; using Microsoft.Net; -using System.Buffers.Writer; namespace LowAllocationWebServer { @@ -63,7 +62,7 @@ static void WriteResponseForGetJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriter(new BufferWriter>(response), true, prettyPrint: false); + JsonWriter jsonWriter = JsonWriter.Create(response, true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < 5; i++) @@ -95,7 +94,7 @@ static void WriteResponseForPostJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - var jsonWriter = new JsonWriter(new BufferWriter>(response), true, prettyPrint: false); + JsonWriter jsonWriter = JsonWriter.Create(response, true, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < requestedCount; i++) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 2dfaf973fd9..a0fad0b18e9 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -10,13 +10,13 @@ namespace System.Text.JsonLab { - public ref struct JsonWriter + public ref struct JsonWriter where TBufferWriter : IBufferWriter { private static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); private static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); private readonly bool _prettyPrint; - private BufferWriter> _bufferWriter; + private BufferWriter _bufferWriter; private readonly bool _isUtf8; private int _indent; @@ -27,7 +27,7 @@ public ref struct JsonWriter /// /// 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(BufferWriter> bufferWriter, bool isUtf8, bool prettyPrint = false) + public JsonWriter(BufferWriter bufferWriter, bool isUtf8, bool prettyPrint = false) { _bufferWriter = bufferWriter; _prettyPrint = prettyPrint; @@ -1557,4 +1557,24 @@ private int AddNewLineAndIndentation(Span buffer) return offset; } } + + //TODO: Move to a separate file + public static class JsonWriter + { + public static JsonWriter Create( + TBufferWriter bufferWriter, + bool isUtf8, + bool prettyPrint = false) where TBufferWriter : IBufferWriter + { + return new JsonWriter(BufferWriter.Create(bufferWriter), isUtf8, prettyPrint); + } + + public static JsonWriter Create( + BufferWriter bufferWriter, + bool isUtf8, + bool prettyPrint = false) where TBufferWriter : IBufferWriter + { + return new JsonWriter(bufferWriter, isUtf8, prettyPrint); + } + } } diff --git a/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index 2e2230034a3..c1146f09367 100644 --- a/tests/Benchmarks/System.IO.Pipelines/E2E.cs +++ b/tests/Benchmarks/System.IO.Pipelines/E2E.cs @@ -2,9 +2,7 @@ // 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; @@ -61,7 +59,7 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Append("\r\n\r\n"); // write body - var writer = new JsonWriter(new BufferWriter>(formatter), true, true); + JsonWriter> writer = JsonWriter.Create(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 d42b232cde4..48428a57a0a 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -2,9 +2,7 @@ // 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; @@ -58,11 +56,10 @@ public void Setup() public void WriterSystemTextJsonBasic() { _arrayFormatter.Clear(); - var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonBasicUtf8(Formatted, bufferWriter, _data); + WriterSystemTextJsonBasicUtf8(Formatted, _arrayFormatter, _data); else - WriterSystemTextJsonBasicUtf16(Formatted, bufferWriter, _data); + WriterSystemTextJsonBasicUtf16(Formatted, _arrayFormatter, _data); } [Benchmark] @@ -75,11 +72,10 @@ public void WriterNewtonsoftBasic() public void WriterSystemTextJsonHelloWorld() { _arrayFormatter.Clear(); - var bufferWriter = new BufferWriter>(_arrayFormatter); if (IsUTF8Encoded) - WriterSystemTextJsonHelloWorldUtf8(Formatted, bufferWriter); + WriterSystemTextJsonHelloWorldUtf8(Formatted, _arrayFormatter); else - WriterSystemTextJsonHelloWorldUtf16(Formatted, bufferWriter); + WriterSystemTextJsonHelloWorldUtf16(Formatted, _arrayFormatter); } [Benchmark] @@ -104,9 +100,9 @@ private TextWriter GetWriter() return writer; } - private static void WriterSystemTextJsonBasicUtf8(bool formatted, BufferWriter> output, int[] data) + private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter output, int[] data) { - var json = new JsonWriter(output, true, formatted); + JsonWriter json = JsonWriter.Create(output, true, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -134,9 +130,9 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, BufferWriter> output, int[] data) + private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatter output, int[] data) { - var json = new JsonWriter(output, false, formatted); + JsonWriter json = JsonWriter.Create(output, false, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -205,9 +201,9 @@ private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int } } - private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, BufferWriter> output) + private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayFormatter output) { - var json = new JsonWriter(output, true, formatted); + JsonWriter json = JsonWriter.Create(output, true, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); @@ -215,9 +211,9 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, BufferWri json.Flush(); } - private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, BufferWriter> output) + private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) { - var json = new JsonWriter(output, false, formatted); + JsonWriter json = JsonWriter.Create(output, false, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index 6cc6e38527d..3c94b0f7668 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -6,8 +6,6 @@ using System.Text.Formatting; using System.Buffers.Text; using System.IO; -using System.Buffers.Writer; -using System.Buffers; namespace System.Text.JsonLab.Tests { @@ -19,8 +17,7 @@ public class JsonWriterTests public void WriteJsonUtf8() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var bufferWriter = new BufferWriter>(formatter); - var json = new JsonWriter(bufferWriter, true, prettyPrint: false); + JsonWriter json = JsonWriter.Create(formatter, true, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -28,8 +25,7 @@ public void WriteJsonUtf8() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - bufferWriter = new BufferWriter>(formatter); - json = new JsonWriter(bufferWriter, true, prettyPrint: true); + json = JsonWriter.Create(formatter, true, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -41,8 +37,7 @@ public void WriteJsonUtf8() public void WriteJsonUtf16() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var bufferWriter = new BufferWriter>(formatter); - var json = new JsonWriter(bufferWriter, false, prettyPrint: false); + JsonWriter json = JsonWriter.Create(formatter, false, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -50,8 +45,7 @@ public void WriteJsonUtf16() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - bufferWriter = new BufferWriter>(formatter); - json = new JsonWriter(bufferWriter, false, prettyPrint: true); + json = JsonWriter.Create(formatter, false, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -60,7 +54,7 @@ public void WriteJsonUtf16() } static string expected = "{\"age\":30,\"first\":\"John\",\"last\":\"Smith\",\"phoneNumbers\":[\"425-000-1212\",\"425-000-1213\",null],\"address\":{\"street\":\"1MicrosoftWay\",\"city\":\"Redmond\",\"zip\":98052},\"values\":[425121,-425122,425123]}"; - static void Write(ref JsonWriter json) + static void Write(ref JsonWriter json) { json.WriteObjectStart(); json.WriteAttribute("age", 30); @@ -93,8 +87,7 @@ public void WriteHelloWorldJsonUtf16(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: false); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var bufferWriter = new BufferWriter>(output); - var jsonUtf16 = new JsonWriter(bufferWriter, false, prettyPrint); + JsonWriter jsonUtf16 = JsonWriter.Create(output, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("message", "Hello, World!"); @@ -115,8 +108,7 @@ public void WriteHelloWorldJsonUtf8(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: true); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var bufferWriter = new BufferWriter>(output); - var jsonUtf8 = new JsonWriter(bufferWriter, true, prettyPrint); + JsonWriter jsonUtf8 = JsonWriter.Create(output, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("message", "Hello, World!"); @@ -139,8 +131,7 @@ public void WriteBasicJsonUtf16(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: false, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - var bufferWriter = new BufferWriter>(output); - var jsonUtf16 = new JsonWriter(bufferWriter, false, prettyPrint); + JsonWriter jsonUtf16 = JsonWriter.Create(output, false, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("age", 42); @@ -184,8 +175,7 @@ public void WriteBasicJsonUtf8(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: true, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - var bufferWriter = new BufferWriter>(output); - var jsonUtf8 = new JsonWriter(bufferWriter, true, prettyPrint); + JsonWriter jsonUtf8 = JsonWriter.Create(output, true, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("age", 42); From ff445a44470c1af990c50eb0b4f18f7c6dee5adc Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 16 Jun 2018 00:28:03 +0100 Subject: [PATCH 08/17] Move static readonly into non-generic --- .../System/Text/Json/JsonWriter.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index a0fad0b18e9..7a9f0ab6562 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -12,9 +12,6 @@ namespace System.Text.JsonLab { public ref struct JsonWriter where TBufferWriter : IBufferWriter { - private static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); - private static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); - private readonly bool _prettyPrint; private BufferWriter _bufferWriter; private readonly bool _isUtf8; @@ -202,11 +199,11 @@ public void WriteObjectEnd() _indent--; if (_isUtf8) { - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), s_newLineUtf8.Length), JsonConstants.CloseBrace); + WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8.Length), JsonConstants.CloseBrace); } else { - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), s_newLineUtf16.Length), JsonConstants.CloseBrace); + WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16.Length), JsonConstants.CloseBrace); } } @@ -289,11 +286,11 @@ public void WriteArrayEnd() if (_isUtf8) { - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), s_newLineUtf8.Length), JsonConstants.CloseBracket); + WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length), JsonConstants.CloseBracket); } else { - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), s_newLineUtf16.Length), JsonConstants.CloseBracket); + WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length), JsonConstants.CloseBracket); } } @@ -706,11 +703,11 @@ public void WriteValue(long value) { if (_isUtf8) { - WriteValueUtf8(value, CalculateValueBytesNeeded(sizeof(byte), s_newLineUtf8.Length)); + WriteValueUtf8(value, CalculateValueBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length)); } else { - WriteValueUtf16(value, CalculateValueBytesNeeded(sizeof(char), s_newLineUtf16.Length)); + WriteValueUtf16(value, CalculateValueBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length)); } } @@ -1345,7 +1342,7 @@ private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf8.Length; // For the new line, \r\n or \n + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length; // For the new line, \r\n or \n bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1368,7 +1365,7 @@ private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extr if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1391,7 +1388,7 @@ private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1414,7 +1411,7 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf8.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1444,7 +1441,7 @@ private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf16.Length; // For the new line, \r\n or \n + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length; // For the new line, \r\n or \n bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1464,7 +1461,7 @@ private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extr if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1484,7 +1481,7 @@ private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1504,7 +1501,7 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS if (_prettyPrint) { - int bytesNeededForPrettyPrint = s_newLineUtf16.Length + 1; // For the new line, \r\n or \n, and the space after the colon + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon bytesNeededForPrettyPrint += (_indent + 1) * 2; bytesNeeded += numBytes * bytesNeededForPrettyPrint; } @@ -1521,7 +1518,7 @@ private int AddNewLineAndIndentation(Span buffer) { int offset = 0; // \r\n versus \n, depending on OS - if (s_newLineUtf8.Length == 2) + if (JsonWriter.s_newLineUtf8Length == 2) buffer[offset++] = JsonConstants.CarriageReturn; buffer[offset++] = JsonConstants.LineFeed; @@ -1541,7 +1538,7 @@ private int AddNewLineAndIndentation(Span buffer) { int offset = 0; // \r\n versus \n, depending on OS - if (s_newLineUtf16.Length == 2) + if (JsonWriter.s_newLineUtf16Length == 2) buffer[offset++] = (char)JsonConstants.CarriageReturn; buffer[offset++] = (char)JsonConstants.LineFeed; @@ -1561,6 +1558,11 @@ private int AddNewLineAndIndentation(Span buffer) //TODO: Move to a separate file public static class JsonWriter { + internal static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); + internal static readonly int s_newLineUtf8Length = s_newLineUtf8.Length; + internal static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); + internal static readonly int s_newLineUtf16Length = s_newLineUtf16.Length; + public static JsonWriter Create( TBufferWriter bufferWriter, bool isUtf8, From 68268450b3ce83f91e709485a3e74bed063d26b2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 16 Jun 2018 01:48:15 +0100 Subject: [PATCH 09/17] Seperate Utf8 & Utf16 --- .../Shared/SampleServer.cs | 4 +- .../System/Text/Json/JsonWriter.Utf16.cs | 866 +++++++++ .../System/Text/Json/JsonWriter.Utf8.cs | 884 +++++++++ .../System/Text/Json/JsonWriter.cs | 1578 +---------------- tests/Benchmarks/System.IO.Pipelines/E2E.cs | 2 +- .../System.Text.JsonLab/JsonWriterPerf.cs | 8 +- .../JsonWriterTests.cs | 44 +- 7 files changed, 1841 insertions(+), 1545 deletions(-) create mode 100644 src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs create mode 100644 src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs diff --git a/samples/LowAllocationWebServer/Shared/SampleServer.cs b/samples/LowAllocationWebServer/Shared/SampleServer.cs index 346a9bda818..89ddb6ce433 100644 --- a/samples/LowAllocationWebServer/Shared/SampleServer.cs +++ b/samples/LowAllocationWebServer/Shared/SampleServer.cs @@ -62,7 +62,7 @@ static void WriteResponseForGetJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - JsonWriter jsonWriter = JsonWriter.Create(response, true, prettyPrint: false); + JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < 5; i++) @@ -94,7 +94,7 @@ static void WriteResponseForPostJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - JsonWriter jsonWriter = JsonWriter.Create(response, true, prettyPrint: false); + JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < requestedCount; i++) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs new file mode 100644 index 00000000000..737e1e0ea6d --- /dev/null +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs @@ -0,0 +1,866 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +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 ref struct JsonWriterUtf16 where TBufferWriter : IBufferWriter + { + private readonly bool _prettyPrint; + private BufferWriter _bufferWriter; + + 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 JsonWriterUtf16(BufferWriter bufferWriter, bool prettyPrint = false) + { + _bufferWriter = bufferWriter; + _prettyPrint = prettyPrint; + + _indent = -1; + _firstItem = true; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to an + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteObjectStart() + { + WriteStart(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + private void WriteStart(int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + + int idx = 0; + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + { + idx = PrettyPrintStart(charBuffer, idx); + } + + charBuffer[idx] = (char)token; + _bufferWriter.Advance(bytesNeeded); + } + + private int PrettyPrintStart(Span charBuffer, int idx) + { + int indent = _indent; + + while (indent-- >= 0) + { + charBuffer[idx++] = (char)JsonConstants.Space; + charBuffer[idx++] = (char)JsonConstants.Space; + } + + return idx; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteObjectStart(string name) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); // quote {name} quote colon open-brace, hence 4 + WriteStart(nameSpan, bytesNeeded, JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + private void WriteStart(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx++] = (char)JsonConstants.Space; + + charBuffer[idx] = (char)token; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + + /// + /// Writes the end tag for an object. + /// + public void WriteObjectEnd() + { + _firstItem = false; + _indent--; + WriteEnd(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16.Length), JsonConstants.CloseBrace); + } + + private void WriteEnd(int bytesNeeded, byte token) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx] = (char)token; + _bufferWriter.Advance(bytesNeeded); + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a nested + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteArrayStart() + { + WriteStart(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteArrayStart(string name) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); + WriteStart(nameSpan, bytesNeeded, JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Writes the end tag for an array. + /// + public void WriteArrayEnd() + { + _firstItem = false; + _indent--; + + WriteEnd(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length), JsonConstants.CloseBracket); + } + + /// + /// Write a quoted string value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The string value that will be quoted within the JSON data. + public void WriteAttribute(string name, string value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + ReadOnlySpan valueSpan = value.AsSpan(); + int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(char)); + WriteAttribute(nameSpan, valueSpan, bytesNeeded); + } + + private void WriteAttribute(ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx++] = (char)JsonConstants.Space; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + valueSpanChar.CopyTo(charBuffer.Slice(idx)); + + charBuffer[idx + valueSpanChar.Length] = (char)JsonConstants.Quote; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + + /// + /// Write a signed integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The signed integer value to be written to JSON data. + public void WriteAttribute(string name, long value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttribute + } + + /// + /// Write an unsigned integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The unsigned integer value to be written to JSON data. + public void WriteAttribute(string name, ulong value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 + } + + /// + /// Write a boolean value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The boolean value to be written to JSON data. + public void WriteAttribute(string name, bool value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTime value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteDateTime(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTimeOffset value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteDateTimeOffset(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, Guid value) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteGuid(value); + } + + /// + /// Write a null value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteAttributeNull(string name) + { + ReadOnlySpan nameSpan = name.AsSpan(); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); + WriteAttribute(nameSpan, bytesNeeded); + WriteJsonValue(JsonConstants.NullValue); + } + + private void WriteAttribute(ReadOnlySpan nameSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + nameSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += nameSpanChar.Length; + + charBuffer[idx++] = (char)JsonConstants.Quote; + + charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + charBuffer[idx] = (char)JsonConstants.Space; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + + /// + /// Writes a quoted string value into the current array. + /// + /// The string value that will be quoted within the JSON data. + public void WriteValue(string value) + { + ReadOnlySpan valueSpan = value.AsSpan(); + int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(char), 2); + WriteValue(valueSpan, bytesNeeded); + } + + private void WriteValue(ReadOnlySpan valueSpanChar, int bytesNeeded) + { + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + int idx = 0; + + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + charBuffer[idx++] = (char)JsonConstants.Quote; + + valueSpanChar.CopyTo(charBuffer.Slice(idx)); + + idx += valueSpanChar.Length; + + charBuffer[idx] = (char)JsonConstants.Quote; + + _bufferWriter.Advance(bytesNeeded); + _firstItem = false; + } + + /// + /// Write a signed integer value into the current array. + /// + /// The signed integer value to be written to JSON data. + public void WriteValue(long value) + { + WriteValue(value, CalculateValueBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length)); + } + + private void WriteValue(long value, int bytesNeeded) + { + bool insertNegationSign = false; + if (value < 0) + { + insertNegationSign = true; + value = -value; + bytesNeeded += sizeof(char); + } + + int digitCount = JsonWriter.CountDigits((ulong)value); + bytesNeeded += sizeof(char) * digitCount; + Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); + + int idx = 0; + if (!_firstItem) + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + + _firstItem = false; + if (_prettyPrint) + idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); + + if (insertNegationSign) + charBuffer[idx++] = '-'; + + WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); + + _bufferWriter.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (char)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (char)('0' + value); + } + + /// + /// Write a unsigned integer value into the current array. + /// + /// The unsigned integer value to be written to JSON data. + public void WriteValue(ulong value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacing(); + + WriteNumber(value); + } + + /// + /// Write a boolean value into the current array. + /// + /// The boolean value to be written to JSON data. + public void WriteValue(bool value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacing(); + + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTime value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacing(); + + WriteDateTime(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTimeOffset value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacing(); + + WriteDateTimeOffset(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(Guid value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperatorUtf16(); + _firstItem = false; + WriteSpacing(); + + WriteGuid(value); + } + + /// + /// Write a null value into the current array. + /// + public void WriteNull() + { + ReadOnlySpan nullLiteral = (BitConverter.IsLittleEndian ? JsonConstants.NullValueUtf16LE : JsonConstants.NullValueUtf16BE); + int charsNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0); + int bytesNeeded = charsNeeded * 2 + nullLiteral.Length; + Span byteBuffer = EnsureBuffer(bytesNeeded); + Span charBuffer = MemoryMarshal.Cast(byteBuffer); + int idx = 0; + if (_firstItem) + { + _firstItem = false; + } + else + { + charBuffer[idx++] = (char)JsonConstants.ListSeperator; + } + + if (_prettyPrint) + { + int indent = _indent; + charBuffer[idx++] = (char)JsonConstants.CarriageReturn; + charBuffer[idx++] = (char)JsonConstants.LineFeed; + + while (indent-- >= 0) + { + charBuffer[idx++] = (char)JsonConstants.Space; + charBuffer[idx++] = (char)JsonConstants.Space; + } + } + + Debug.Assert(byteBuffer.Slice(idx * 2).Length >= nullLiteral.Length); + nullLiteral.CopyTo(byteBuffer.Slice(idx * 2)); + + _bufferWriter.Advance(bytesNeeded); + } + + public void Flush() => _bufferWriter.Flush(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(long value) + { + //TODO: Optimize, this is too slow + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(ulong value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTime(DateTime value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTimeOffset(DateTimeOffset value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteGuid(Guid value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteJsonValue(ReadOnlySpan values) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteControl(byte value) + { + Span buffer = EnsureBuffer(2); + Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; + _bufferWriter.Advance(2); + } + + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteItemSeperatorUtf16() + { + if (_firstItem) return; + + WriteControl(JsonConstants.ListSeperator); + } + + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacing(bool newline = true) + { + if (!_prettyPrint) return; + + var indent = _indent; + var bytesNeeded = newline ? 2 : 0; + bytesNeeded += (indent + 1) * 2; + bytesNeeded *= sizeof(char); + + Span buffer = EnsureBuffer(bytesNeeded); + var span = MemoryMarshal.Cast(buffer); + ref char utf16Bytes = ref MemoryMarshal.GetReference(span); + int idx = 0; + + if (newline) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.CarriageReturn; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.LineFeed; + } + + while (indent-- >= 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + } + + _bufferWriter.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span EnsureBuffer(int needed = 256) + { + _bufferWriter.Ensure(needed); + Span buffer = _bufferWriter.Buffer; + if (buffer.Length < needed) + JsonThrowHelper.ThrowOutOfMemoryException(); + return buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateStartBytesNeeded(int numBytes) + { + int bytesNeeded = numBytes; + + if (!_firstItem) + bytesNeeded *= 2; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateEndBytesNeeded(int numBytes, int newLineLength) + { + int bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateValueBytesNeeded(int numBytes, int newLineLength) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + bytesNeeded += MemoryMarshal.AsBytes(span).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + bytesNeeded += MemoryMarshal.AsBytes(span).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 + bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 + bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; + bytesNeeded += MemoryMarshal.AsBytes(valueSpan).Length; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddNewLineAndIndentation(Span buffer) + { + int offset = 0; + // \r\n versus \n, depending on OS + if (JsonWriter.s_newLineUtf8Length == 2) + buffer[offset++] = JsonConstants.CarriageReturn; + + buffer[offset++] = JsonConstants.LineFeed; + + int indent = _indent; + + while (indent-- >= 0) + { + buffer[offset++] = JsonConstants.Space; + buffer[offset++] = JsonConstants.Space; + } + return offset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddNewLineAndIndentation(Span buffer) + { + int offset = 0; + // \r\n versus \n, depending on OS + if (JsonWriter.s_newLineUtf16Length == 2) + buffer[offset++] = (char)JsonConstants.CarriageReturn; + + buffer[offset++] = (char)JsonConstants.LineFeed; + + int indent = _indent; + + while (indent-- >= 0) + { + buffer[offset++] = (char)JsonConstants.Space; + buffer[offset++] = (char)JsonConstants.Space; + } + + return offset; + } + } +} diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs new file mode 100644 index 00000000000..707b5edaa2b --- /dev/null +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs @@ -0,0 +1,884 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +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 ref struct JsonWriterUtf8 where TBufferWriter : IBufferWriter + { + private readonly bool _prettyPrint; + private BufferWriter _bufferWriter; + + 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 JsonWriterUtf8(BufferWriter bufferWriter, bool prettyPrint = false) + { + _bufferWriter = bufferWriter; + _prettyPrint = prettyPrint; + + _indent = -1; + _firstItem = true; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to an + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteObjectStart() + { + WriteStart(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + private void WriteStart(int bytesNeeded, byte token) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + + int idx = 0; + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + { + idx = PrettyPrintStart(byteBuffer, idx); + } + + byteBuffer[idx] = token; + _bufferWriter.Advance(bytesNeeded); + } + + private int PrettyPrintStart(Span byteBuffer, int idx) + { + int indent = _indent; + + while (indent-- >= 0) + { + byteBuffer[idx++] = JsonConstants.Space; + byteBuffer[idx++] = JsonConstants.Space; + } + + return idx; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteObjectStart(string name) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); // quote {name} quote colon open-brace, hence 4 + WriteStart(nameSpan, bytesNeeded, JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + private void WriteStart(ReadOnlySpan nameSpanByte, int bytesNeeded, byte token) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx++] = JsonConstants.Quote; + + OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); + Debug.Assert(consumed == nameSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + byteBuffer[idx++] = JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + byteBuffer[idx++] = JsonConstants.Space; + + byteBuffer[idx++] = token; + + _bufferWriter.Advance(idx); + _firstItem = false; + } + + /// + /// Writes the end tag for an object. + /// + public void WriteObjectEnd() + { + _firstItem = false; + _indent--; + WriteEnd(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8.Length), JsonConstants.CloseBrace); + } + + private void WriteEnd(int bytesNeeded, byte token) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx] = token; + _bufferWriter.Advance(bytesNeeded); + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a nested + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteArrayStart() + { + WriteStart(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteArrayStart(string name) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); + WriteStart(nameSpan, bytesNeeded, JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Writes the end tag for an array. + /// + public void WriteArrayEnd() + { + _firstItem = false; + _indent--; + + WriteEnd(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length), JsonConstants.CloseBracket); + } + + /// + /// Write a quoted string value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The string value that will be quoted within the JSON data. + public void WriteAttribute(string name, string value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); + int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(byte)); + WriteAttribute(nameSpan, valueSpan, bytesNeeded); + } + + private void WriteAttribute(ReadOnlySpan nameSpanByte, ReadOnlySpan valueSpanByte, int bytesNeeded) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx++] = JsonConstants.Quote; + + OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); + Debug.Assert(consumed == nameSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + byteBuffer[idx++] = JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + byteBuffer[idx++] = JsonConstants.Space; + + byteBuffer[idx++] = JsonConstants.Quote; + + status = Encodings.Utf16.ToUtf8(valueSpanByte, byteBuffer.Slice(idx), out consumed, out written); + Debug.Assert(consumed == valueSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + _bufferWriter.Advance(idx); + _firstItem = false; + } + + /// + /// Write a signed integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The signed integer value to be written to JSON data. + public void WriteAttribute(string name, long value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttribute + } + + /// + /// Write an unsigned integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The unsigned integer value to be written to JSON data. + public void WriteAttribute(string name, ulong value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttribute + } + + /// + /// Write a boolean value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The boolean value to be written to JSON data. + public void WriteAttribute(string name, bool value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTime value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteDateTime(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTimeOffset value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteDateTimeOffset(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, Guid value) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteGuid(value); + } + + /// + /// Write a null value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteAttributeNull(string name) + { + ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); + int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); + WriteAttribute(nameSpan, bytesNeeded); + WriteJsonValue(JsonConstants.NullValue); + } + + private void WriteAttribute(ReadOnlySpan nameSpanByte, int bytesNeeded) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx++] = JsonConstants.Quote; + + OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); + Debug.Assert(consumed == nameSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + byteBuffer[idx++] = JsonConstants.KeyValueSeperator; + + if (_prettyPrint) + byteBuffer[idx++] = JsonConstants.Space; + + _bufferWriter.Advance(idx); + _firstItem = false; + } + + /// + /// Writes a quoted string value into the current array. + /// + /// The string value that will be quoted within the JSON data. + public void WriteValue(string value) + { + ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); + int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(byte), 2); + WriteValue(valueSpan, bytesNeeded); + } + + private void WriteValue(ReadOnlySpan valueSpanByte, int bytesNeeded) + { + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + byteBuffer[idx++] = JsonConstants.Quote; + + + OperationStatus status = Encodings.Utf16.ToUtf8(valueSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); + Debug.Assert(consumed == valueSpanByte.Length); + if (status != OperationStatus.Done) + { + JsonThrowHelper.ThrowFormatException(); + } + + idx += written; + + byteBuffer[idx++] = JsonConstants.Quote; + + _bufferWriter.Advance(idx); + _firstItem = false; + } + + /// + /// Write a signed integer value into the current array. + /// + /// The signed integer value to be written to JSON data. + public void WriteValue(long value) + { + WriteValue(value, CalculateValueBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length)); + } + + private void WriteValue(long value, int bytesNeeded) + { + bool insertNegationSign = false; + if (value < 0) + { + insertNegationSign = true; + value = -value; + bytesNeeded += sizeof(byte); + } + + int digitCount = JsonWriter.CountDigits((ulong)value); + bytesNeeded += sizeof(byte) * digitCount; + Span byteBuffer = EnsureBuffer(bytesNeeded); + + int idx = 0; + if (!_firstItem) + byteBuffer[idx++] = JsonConstants.ListSeperator; + + _firstItem = false; + if (_prettyPrint) + idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); + + if (insertNegationSign) + byteBuffer[idx++] = (byte)'-'; + + WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); + + _bufferWriter.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (byte)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (byte)('0' + value); + } + + /// + /// Write a unsigned integer value into the current array. + /// + /// The unsigned integer value to be written to JSON data. + public void WriteValue(ulong value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + + WriteNumber(value); + } + + /// + /// Write a boolean value into the current array. + /// + /// The boolean value to be written to JSON data. + public void WriteValue(bool value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTime value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + + WriteDateTime(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTimeOffset value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + + WriteDateTimeOffset(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(Guid value) + { + //TODO: Optimize, just like WriteValue(long value) + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + + WriteGuid(value); + } + + /// + /// Write a null value into the current array. + /// + public void WriteNull() + { + int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; + Span byteBuffer = EnsureBuffer(bytesNeeded); + int idx = 0; + if (_firstItem) + { + _firstItem = false; + } + else + { + byteBuffer[idx++] = JsonConstants.ListSeperator; + } + + if (_prettyPrint) + { + int indent = _indent; + byteBuffer[idx++] = JsonConstants.CarriageReturn; + byteBuffer[idx++] = JsonConstants.LineFeed; + + while (indent-- >= 0) + { + byteBuffer[idx++] = JsonConstants.Space; + byteBuffer[idx++] = JsonConstants.Space; + } + } + + Debug.Assert(byteBuffer.Slice(idx).Length >= JsonConstants.NullValue.Length); + JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); + + _bufferWriter.Advance(bytesNeeded); + } + + public void Flush() => _bufferWriter.Flush(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(long value) + { + //TODO: Optimize, this is too slow + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(ulong value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTime(DateTime value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTimeOffset(DateTimeOffset value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteGuid(Guid value) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf8)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteJsonValue(ReadOnlySpan values) + { + Span buffer = _bufferWriter.Buffer; + int written; + while (!SymbolTable.InvariantUtf8.TryEncode(values, buffer, out int consumed, out written)) + { + buffer = EnsureBuffer(); + } + + _bufferWriter.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteControl(byte value) + { + Span buffer = EnsureBuffer(1); + MemoryMarshal.GetReference(buffer) = value; + _bufferWriter.Advance(1); + } + + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteItemSeperator() + { + if (_firstItem) return; + + WriteControl(JsonConstants.ListSeperator); + } + + // TODO: Once public methods are optimized, remove this. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacing(bool newline = true) + { + if (!_prettyPrint) return; + + var indent = _indent; + var bytesNeeded = newline ? 2 : 0; + bytesNeeded += (indent + 1) * 2; + + Span buffer = EnsureBuffer(bytesNeeded); + ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer); + int idx = 0; + + if (newline) + { + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.CarriageReturn; + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.LineFeed; + } + + while (indent-- >= 0) + { + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; + } + + _bufferWriter.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span EnsureBuffer(int needed = 256) + { + _bufferWriter.Ensure(needed); + Span buffer = _bufferWriter.Buffer; + if (buffer.Length < needed) + JsonThrowHelper.ThrowOutOfMemoryException(); + return buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateStartBytesNeeded(int numBytes) + { + int bytesNeeded = numBytes; + + if (!_firstItem) + bytesNeeded *= 2; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateEndBytesNeeded(int numBytes, int newLineLength) + { + int bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int CalculateValueBytesNeeded(int numBytes, int newLineLength) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + return bytesNeeded; + } + + private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length; // For the new line, \r\n or \n + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + + if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) + { + JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + } + bytesNeeded += bytesNeededValue; + return bytesNeeded; + } + + private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * extraCharacterCount; + + if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) + { + JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + } + bytesNeeded += bytesNeededValue; + return bytesNeeded; + } + + private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 + + if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) + { + JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + } + bytesNeeded += bytesNeededValue; + return bytesNeeded; + } + + private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) + { + int bytesNeeded = 0; + if (!_firstItem) + bytesNeeded = numBytes; + + if (_prettyPrint) + { + int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon + bytesNeededForPrettyPrint += (_indent + 1) * 2; + bytesNeeded += numBytes * bytesNeededForPrettyPrint; + } + + bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 + + if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededName) != OperationStatus.Done) + { + JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + } + if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) + { + JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + } + bytesNeeded += bytesNeededName; + bytesNeeded += bytesNeededValue; + + return bytesNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddNewLineAndIndentation(Span buffer) + { + int offset = 0; + // \r\n versus \n, depending on OS + if (JsonWriter.s_newLineUtf8Length == 2) + buffer[offset++] = JsonConstants.CarriageReturn; + + buffer[offset++] = JsonConstants.LineFeed; + + int indent = _indent; + + while (indent-- >= 0) + { + buffer[offset++] = JsonConstants.Space; + buffer[offset++] = JsonConstants.Space; + } + return offset; + } + } +} diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 7a9f0ab6562..26150dd42ab 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Buffers; -using System.Buffers.Text; using System.Buffers.Writer; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -10,1573 +9,96 @@ namespace System.Text.JsonLab { - public ref struct JsonWriter where TBufferWriter : IBufferWriter + public static class JsonWriter { - private readonly bool _prettyPrint; - private BufferWriter _bufferWriter; - private readonly bool _isUtf8; - - 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(BufferWriter bufferWriter, bool isUtf8, bool prettyPrint = false) - { - _bufferWriter = bufferWriter; - _prettyPrint = prettyPrint; - _isUtf8 = isUtf8; - - _indent = -1; - _firstItem = true; - } - - /// - /// Write the starting tag of an object. This is used for adding an object to an - /// array of other items. If this is used while inside a nested object, the property - /// name will be missing and result in invalid JSON. - /// - public void WriteObjectStart() - { - if (_isUtf8) - { - WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBrace); - } - else - { - WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBrace); - } - - _firstItem = true; - _indent++; - } - - private void WriteStartUtf8(int bytesNeeded, byte token) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - - int idx = 0; - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - if (_prettyPrint) - { - int indent = _indent; - - while (indent-- >= 0) - { - byteBuffer[idx++] = JsonConstants.Space; - byteBuffer[idx++] = JsonConstants.Space; - } - } - - byteBuffer[idx] = token; - _bufferWriter.Advance(bytesNeeded); - } - - private void WriteStartUtf16(int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - - int idx = 0; - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - { - int indent = _indent; - - while (indent-- >= 0) - { - charBuffer[idx++] = (char)JsonConstants.Space; - charBuffer[idx++] = (char)JsonConstants.Space; - } - } - - charBuffer[idx] = (char)token; - _bufferWriter.Advance(bytesNeeded); - } - - /// - /// Write the starting tag of an object. This is used for adding an object to a - /// nested object. If this is used while inside a nested array, the property - /// name will be written and result in invalid JSON. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteObjectStart(string name) - { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); // quote {name} quote colon open-brace, hence 4 - WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBrace); - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); // quote {name} quote colon open-brace, hence 4 - WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBrace); - } - - _firstItem = true; - _indent++; - } - - private void WriteStartUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded, byte token) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - byteBuffer[idx++] = JsonConstants.Quote; - - OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); - Debug.Assert(consumed == nameSpanByte.Length); - if (status != OperationStatus.Done) - { - JsonThrowHelper.ThrowFormatException(); - } - - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - byteBuffer[idx++] = JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - byteBuffer[idx++] = JsonConstants.Space; - - byteBuffer[idx++] = token; - - _bufferWriter.Advance(idx); - _firstItem = false; - } - - private void WriteStartUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx++] = (char)JsonConstants.Space; - - charBuffer[idx] = (char)token; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Writes the end tag for an object. - /// - public void WriteObjectEnd() - { - _firstItem = false; - _indent--; - if (_isUtf8) - { - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8.Length), JsonConstants.CloseBrace); - } - else - { - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16.Length), JsonConstants.CloseBrace); - } - } - - private void WriteEndUtf8(int bytesNeeded, byte token) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - byteBuffer[idx] = token; - _bufferWriter.Advance(bytesNeeded); - } - - private void WriteEndUtf16(int bytesNeeded, byte token) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx] = (char)token; - _bufferWriter.Advance(bytesNeeded); - } - - /// - /// Write the starting tag of an array. This is used for adding an array to a nested - /// array of other items. If this is used while inside a nested object, the property - /// name will be missing and result in invalid JSON. - /// - public void WriteArrayStart() - { - if (_isUtf8) - { - WriteStartUtf8(CalculateStartBytesNeeded(sizeof(byte)), JsonConstants.OpenBracket); - } - else - { - WriteStartUtf16(CalculateStartBytesNeeded(sizeof(char)), JsonConstants.OpenBracket); - } - - _firstItem = true; - _indent++; - } - - /// - /// Write the starting tag of an array. This is used for adding an array to a - /// nested object. If this is used while inside a nested array, the property - /// name will be written and result in invalid JSON. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteArrayStart(string name) - { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(byte), 4); - WriteStartUtf8(nameSpan, bytesNeeded, JsonConstants.OpenBracket); - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateBytesNeeded(nameSpan, sizeof(char), 4); - WriteStartUtf16(nameSpan, bytesNeeded, JsonConstants.OpenBracket); - } - - _firstItem = true; - _indent++; - } - - /// - /// Writes the end tag for an array. - /// - public void WriteArrayEnd() - { - _firstItem = false; - _indent--; - - if (_isUtf8) - { - WriteEndUtf8(CalculateEndBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length), JsonConstants.CloseBracket); - } - else - { - WriteEndUtf16(CalculateEndBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length), JsonConstants.CloseBracket); - } - } + internal static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); + internal static readonly int s_newLineUtf8Length = s_newLineUtf8.Length; + internal static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); + internal static readonly int s_newLineUtf16Length = s_newLineUtf16.Length; - /// - /// Write a quoted string value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The string value that will be quoted within the JSON data. - public void WriteAttribute(string name, string value) + public static JsonWriterUtf8 CreateUtf8( + TBufferWriter bufferWriter, + bool prettyPrint = false) where TBufferWriter : IBufferWriter { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); - int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, valueSpan, bytesNeeded); - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - ReadOnlySpan valueSpan = value.AsSpan(); - int bytesNeeded = CalculateAttributeBytesNeeded(nameSpan, valueSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, valueSpan, bytesNeeded); - } + return new JsonWriterUtf8(BufferWriter.Create(bufferWriter), prettyPrint); } - private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, ReadOnlySpan valueSpanByte, int bytesNeeded) + public static JsonWriterUtf8 Create( + BufferWriter bufferWriter, + bool prettyPrint = false) where TBufferWriter : IBufferWriter { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - byteBuffer[idx++] = JsonConstants.Quote; - - OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); - Debug.Assert(consumed == nameSpanByte.Length); - if (status != OperationStatus.Done) - { - JsonThrowHelper.ThrowFormatException(); - } - - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - byteBuffer[idx++] = JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - byteBuffer[idx++] = JsonConstants.Space; - - byteBuffer[idx++] = JsonConstants.Quote; - - status = Encodings.Utf16.ToUtf8(valueSpanByte, byteBuffer.Slice(idx), out consumed, out written); - Debug.Assert(consumed == valueSpanByte.Length); - if (status != OperationStatus.Done) - { - JsonThrowHelper.ThrowFormatException(); - } - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - _bufferWriter.Advance(idx); - _firstItem = false; + return new JsonWriterUtf8(bufferWriter, prettyPrint); } - private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, ReadOnlySpan valueSpanChar, int bytesNeeded) + public static JsonWriterUtf16 CreateUtf16( + TBufferWriter bufferWriter, + bool prettyPrint = false) where TBufferWriter : IBufferWriter { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx++] = (char)JsonConstants.Space; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - valueSpanChar.CopyTo(charBuffer.Slice(idx)); - - charBuffer[idx + valueSpanChar.Length] = (char)JsonConstants.Quote; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; + return new JsonWriterUtf16(BufferWriter.Create(bufferWriter), prettyPrint); } - /// - /// Write a signed integer value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The signed integer value to be written to JSON data. - public void WriteAttribute(string name, long value) + public static JsonWriterUtf16 CreateUtf16( + BufferWriter bufferWriter, + bool prettyPrint = false) where TBufferWriter : IBufferWriter { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteNumberUtf8(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteNumberUtf16(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 - } + return new JsonWriterUtf16(bufferWriter, prettyPrint); } - /// - /// Write an unsigned integer value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The unsigned integer value to be written to JSON data. - public void WriteAttribute(string name, ulong value) - { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf8 - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteNumber(value); //TODO: attempt to optimize by combining this with WriteAttributeUtf16 - } - } - /// - /// Write a boolean value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The boolean value to be written to JSON data. - public void WriteAttribute(string name, bool value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int CountDigits(ulong value) { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - if (value) - WriteJsonValueUtf8(JsonConstants.TrueValue); - else - WriteJsonValueUtf8(JsonConstants.FalseValue); - } - else + int digits = 1; + uint part; + if (value >= 10000000) { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - if (value) - WriteJsonValueUtf16(JsonConstants.TrueValue); + if (value >= 100000000000000) + { + part = (uint)(value / 100000000000000); + digits += 14; + } else - WriteJsonValueUtf16(JsonConstants.FalseValue); - } - } - - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, DateTime value) - { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteDateTime(value); - } - else - { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteDateTime(value); - } - } - - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, DateTimeOffset value) - { - if (_isUtf8) - { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteDateTimeOffset(value); + { + part = (uint)(value / 10000000); + digits += 7; + } } else { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteDateTimeOffset(value); + part = (uint)value; } - } - /// - /// Write a value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - /// The value to be written to JSON data. - public void WriteAttribute(string name, Guid value) - { - if (_isUtf8) + if (part < 10) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteGuid(value); + // no-op } - else + else if (part < 100) { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteGuid(value); + digits += 1; } - } - - /// - /// Write a null value along with a property name into the current object. - /// - /// The name of the property (i.e. key) within the containing object. - public void WriteAttributeNull(string name) - { - if (_isUtf8) + else if (part < 1000) { - ReadOnlySpan nameSpan = MemoryMarshal.AsBytes(name.AsSpan()); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(byte)); - WriteAttributeUtf8(nameSpan, bytesNeeded); - WriteJsonValueUtf8(JsonConstants.NullValue); + digits += 2; } - else + else if (part < 10000) { - ReadOnlySpan nameSpan = name.AsSpan(); - int bytesNeeded = CalculateStartAttributeBytesNeeded(nameSpan, sizeof(char)); - WriteAttributeUtf16(nameSpan, bytesNeeded); - WriteJsonValueUtf16(JsonConstants.NullValue); + digits += 3; } - } - - private void WriteAttributeUtf8(ReadOnlySpan nameSpanByte, int bytesNeeded) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - byteBuffer[idx++] = JsonConstants.Quote; - - OperationStatus status = Encodings.Utf16.ToUtf8(nameSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); - Debug.Assert(consumed == nameSpanByte.Length); - if (status != OperationStatus.Done) + else if (part < 100000) { - JsonThrowHelper.ThrowFormatException(); + digits += 4; } - - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - byteBuffer[idx++] = JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - byteBuffer[idx++] = JsonConstants.Space; - - _bufferWriter.Advance(idx); - _firstItem = false; - } - - private void WriteAttributeUtf16(ReadOnlySpan nameSpanChar, int bytesNeeded) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - nameSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += nameSpanChar.Length; - - charBuffer[idx++] = (char)JsonConstants.Quote; - - charBuffer[idx++] = (char)JsonConstants.KeyValueSeperator; - - if (_prettyPrint) - charBuffer[idx] = (char)JsonConstants.Space; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Writes a quoted string value into the current array. - /// - /// The string value that will be quoted within the JSON data. - public void WriteValue(string value) - { - if (_isUtf8) + else if (part < 1000000) { - ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(byte), 2); - WriteValueUtf8(valueSpan, bytesNeeded); + digits += 5; } else { - ReadOnlySpan valueSpan = value.AsSpan(); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(char), 2); - WriteValueUtf16(valueSpan, bytesNeeded); - } - } - - private void WriteValueUtf8(ReadOnlySpan valueSpanByte, int bytesNeeded) - { - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - byteBuffer[idx++] = JsonConstants.Quote; - - - OperationStatus status = Encodings.Utf16.ToUtf8(valueSpanByte, byteBuffer.Slice(idx), out int consumed, out int written); - Debug.Assert(consumed == valueSpanByte.Length); - if (status != OperationStatus.Done) - { - JsonThrowHelper.ThrowFormatException(); - } - - idx += written; - - byteBuffer[idx++] = JsonConstants.Quote; - - _bufferWriter.Advance(idx); - _firstItem = false; - } - - private void WriteValueUtf16(ReadOnlySpan valueSpanChar, int bytesNeeded) - { - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - int idx = 0; - - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - charBuffer[idx++] = (char)JsonConstants.Quote; - - valueSpanChar.CopyTo(charBuffer.Slice(idx)); - - idx += valueSpanChar.Length; - - charBuffer[idx] = (char)JsonConstants.Quote; - - _bufferWriter.Advance(bytesNeeded); - _firstItem = false; - } - - /// - /// Write a signed integer value into the current array. - /// - /// The signed integer value to be written to JSON data. - public void WriteValue(long value) - { - if (_isUtf8) - { - WriteValueUtf8(value, CalculateValueBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length)); - } - else - { - WriteValueUtf16(value, CalculateValueBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length)); - } - } - - private void WriteValueUtf8(long value, int bytesNeeded) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - bytesNeeded += sizeof(byte); - } - - int digitCount = CountDigits((ulong)value); - bytesNeeded += sizeof(byte) * digitCount; - Span byteBuffer = EnsureBuffer(bytesNeeded); - - int idx = 0; - if (!_firstItem) - byteBuffer[idx++] = JsonConstants.ListSeperator; - - _firstItem = false; - if (_prettyPrint) - idx += AddNewLineAndIndentation(byteBuffer.Slice(idx)); - - if (insertNegationSign) - byteBuffer[idx++] = (byte)'-'; - - WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); - - _bufferWriter.Advance(bytesNeeded); - } - - private void WriteValueUtf16(long value, int bytesNeeded) - { - bool insertNegationSign = false; - if (value < 0) - { - insertNegationSign = true; - value = -value; - bytesNeeded += sizeof(char); - } - - int digitCount = CountDigits((ulong)value); - bytesNeeded += sizeof(char) * digitCount; - Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); - - int idx = 0; - if (!_firstItem) - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - - _firstItem = false; - if (_prettyPrint) - idx += AddNewLineAndIndentation(charBuffer.Slice(idx)); - - if (insertNegationSign) - charBuffer[idx++] = '-'; - - WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); - - _bufferWriter.Advance(bytesNeeded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (byte)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (byte)('0' + value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (char)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (char)('0' + value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CountDigits(ulong value) - { - int digits = 1; - uint part; - if (value >= 10000000) - { - if (value >= 100000000000000) - { - part = (uint)(value / 100000000000000); - digits += 14; - } - else - { - part = (uint)(value / 10000000); - digits += 7; - } - } - else - { - part = (uint)value; - } - - if (part < 10) - { - // no-op - } - else if (part < 100) - { - digits += 1; - } - else if (part < 1000) - { - digits += 2; - } - else if (part < 10000) - { - digits += 3; - } - else if (part < 100000) - { - digits += 4; - } - else if (part < 1000000) - { - digits += 5; - } - else - { - Debug.Assert(part < 10000000); - digits += 6; + Debug.Assert(part < 10000000); + digits += 6; } return digits; } - - /// - /// Write a unsigned integer value into the current array. - /// - /// The unsigned integer value to be written to JSON data. - public void WriteValue(ulong value) - { - if (_isUtf8) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); - - WriteNumber(value); - } - else - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteNumber(value); - } - } - - /// - /// Write a boolean value into the current array. - /// - /// The boolean value to be written to JSON data. - public void WriteValue(bool value) - { - if (_isUtf8) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); - - if (value) - WriteJsonValueUtf8(JsonConstants.TrueValue); - else - WriteJsonValueUtf8(JsonConstants.FalseValue); - } - else - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - if (value) - WriteJsonValueUtf16(JsonConstants.TrueValue); - else - WriteJsonValueUtf16(JsonConstants.FalseValue); - } - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(DateTime value) - { - if (_isUtf8) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); - - WriteDateTime(value); - } - else - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteDateTime(value); - } - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(DateTimeOffset value) - { - if (_isUtf8) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); - - WriteDateTimeOffset(value); - } - else - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteDateTimeOffset(value); - } - } - - /// - /// Write a value into the current array. - /// - /// The value to be written to JSON data. - public void WriteValue(Guid value) - { - if (_isUtf8) - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf8(); - _firstItem = false; - WriteSpacingUtf8(); - - WriteGuid(value); - } - else - { - //TODO: Optimize, just like WriteValue(long value) - WriteItemSeperatorUtf16(); - _firstItem = false; - WriteSpacingUtf16(); - - WriteGuid(value); - } - } - - /// - /// Write a null value into the current array. - /// - public void WriteNull() - { - if (_isUtf8) - { - int bytesNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0) + JsonConstants.NullValue.Length; - Span byteBuffer = EnsureBuffer(bytesNeeded); - int idx = 0; - if (_firstItem) - { - _firstItem = false; - } - else - { - byteBuffer[idx++] = JsonConstants.ListSeperator; - } - - if (_prettyPrint) - { - int indent = _indent; - byteBuffer[idx++] = JsonConstants.CarriageReturn; - byteBuffer[idx++] = JsonConstants.LineFeed; - - while (indent-- >= 0) - { - byteBuffer[idx++] = JsonConstants.Space; - byteBuffer[idx++] = JsonConstants.Space; - } - } - - Debug.Assert(byteBuffer.Slice(idx).Length >= JsonConstants.NullValue.Length); - JsonConstants.NullValue.CopyTo(byteBuffer.Slice(idx)); - - _bufferWriter.Advance(bytesNeeded); - } - else - { - ReadOnlySpan nullLiteral = (BitConverter.IsLittleEndian ? JsonConstants.NullValueUtf16LE : JsonConstants.NullValueUtf16BE); - int charsNeeded = (_firstItem ? 0 : 1) + (_prettyPrint ? 2 + (_indent + 1) * 2 : 0); - int bytesNeeded = charsNeeded * 2 + nullLiteral.Length; - Span byteBuffer = EnsureBuffer(bytesNeeded); - Span charBuffer = MemoryMarshal.Cast(byteBuffer); - int idx = 0; - if (_firstItem) - { - _firstItem = false; - } - else - { - charBuffer[idx++] = (char)JsonConstants.ListSeperator; - } - - if (_prettyPrint) - { - int indent = _indent; - charBuffer[idx++] = (char)JsonConstants.CarriageReturn; - charBuffer[idx++] = (char)JsonConstants.LineFeed; - - while (indent-- >= 0) - { - charBuffer[idx++] = (char)JsonConstants.Space; - charBuffer[idx++] = (char)JsonConstants.Space; - } - } - - Debug.Assert(byteBuffer.Slice(idx * 2).Length >= nullLiteral.Length); - nullLiteral.CopyTo(byteBuffer.Slice(idx * 2)); - - _bufferWriter.Advance(bytesNeeded); - } - } - - public void Flush() => _bufferWriter.Flush(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumberUtf8(long value) - { - //TODO: Optimize, this is too slow - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumberUtf16(long value) - { - //TODO: Optimize, this is too slow - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf16)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNumber(ulong value) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteDateTime(DateTime value) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteDateTimeOffset(DateTimeOffset value) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.DateTimeFormat, SymbolTable.InvariantUtf8)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteGuid(Guid value) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!CustomFormatter.TryFormat(value, buffer, out written, JsonConstants.GuidFormat, SymbolTable.InvariantUtf8)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteJsonValueUtf8(ReadOnlySpan values) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!SymbolTable.InvariantUtf8.TryEncode(values, buffer, out int consumed, out written)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteJsonValueUtf16(ReadOnlySpan values) - { - Span buffer = _bufferWriter.Buffer; - int written; - while (!SymbolTable.InvariantUtf16.TryEncode(values, buffer, out int consumed, out written)) - { - buffer = EnsureBuffer(); - } - - _bufferWriter.Advance(written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteControlUtf8(byte value) - { - Span buffer = EnsureBuffer(1); - MemoryMarshal.GetReference(buffer) = value; - _bufferWriter.Advance(1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteControlUtf16(byte value) - { - Span buffer = EnsureBuffer(2); - Unsafe.As(ref MemoryMarshal.GetReference(buffer)) = (char)value; - _bufferWriter.Advance(2); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteItemSeperatorUtf8() - { - if (_firstItem) return; - - WriteControlUtf8(JsonConstants.ListSeperator); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteItemSeperatorUtf16() - { - if (_firstItem) return; - - WriteControlUtf16(JsonConstants.ListSeperator); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteSpacingUtf8(bool newline = true) - { - if (!_prettyPrint) return; - - var indent = _indent; - var bytesNeeded = newline ? 2 : 0; - bytesNeeded += (indent + 1) * 2; - - Span buffer = EnsureBuffer(bytesNeeded); - ref byte utf8Bytes = ref MemoryMarshal.GetReference(buffer); - int idx = 0; - - if (newline) - { - Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.CarriageReturn; - Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.LineFeed; - } - - while (indent-- >= 0) - { - Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; - Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; - } - - _bufferWriter.Advance(bytesNeeded); - } - - // TODO: Once public methods are optimized, remove this. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteSpacingUtf16(bool newline = true) - { - if (!_prettyPrint) return; - - var indent = _indent; - var bytesNeeded = newline ? 2 : 0; - bytesNeeded += (indent + 1) * 2; - bytesNeeded *= sizeof(char); - - Span buffer = EnsureBuffer(bytesNeeded); - var span = MemoryMarshal.Cast(buffer); - ref char utf16Bytes = ref MemoryMarshal.GetReference(span); - int idx = 0; - - if (newline) - { - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.CarriageReturn; - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.LineFeed; - } - - while (indent-- >= 0) - { - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; - Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; - } - - _bufferWriter.Advance(bytesNeeded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Span EnsureBuffer(int needed = 256) - { - _bufferWriter.Ensure(needed); - Span buffer = _bufferWriter.Buffer; - if (buffer.Length < needed) - JsonThrowHelper.ThrowOutOfMemoryException(); - return buffer; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateStartBytesNeeded(int numBytes) - { - int bytesNeeded = numBytes; - - if (!_firstItem) - bytesNeeded *= 2; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateEndBytesNeeded(int numBytes, int newLineLength) - { - int bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateValueBytesNeeded(int numBytes, int newLineLength) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = newLineLength; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - return bytesNeeded; - } - - private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - - if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) - { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); - } - bytesNeeded += bytesNeededValue; - return bytesNeeded; - } - - private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - - if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) - { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); - } - bytesNeeded += bytesNeededValue; - return bytesNeeded; - } - - private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 - - if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) - { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); - } - bytesNeeded += bytesNeededValue; - return bytesNeeded; - } - - private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf8Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 - - if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededName) != OperationStatus.Done) - { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); - } - if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) - { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); - } - bytesNeeded += bytesNeededName; - bytesNeeded += bytesNeededValue; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length; // For the new line, \r\n or \n - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - bytesNeeded += MemoryMarshal.AsBytes(span).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extraCharacterCount) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * extraCharacterCount; - bytesNeeded += MemoryMarshal.AsBytes(span).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 3; // quote {name} quote colon, hence 3 - bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlySpan valueSpan, int numBytes) - { - int bytesNeeded = 0; - if (!_firstItem) - bytesNeeded = numBytes; - - if (_prettyPrint) - { - int bytesNeededForPrettyPrint = JsonWriter.s_newLineUtf16Length + 1; // For the new line, \r\n or \n, and the space after the colon - bytesNeededForPrettyPrint += (_indent + 1) * 2; - bytesNeeded += numBytes * bytesNeededForPrettyPrint; - } - - bytesNeeded += numBytes * 5; //quote {name} quote colon quote {value} quote, hence 5 - bytesNeeded += MemoryMarshal.AsBytes(nameSpan).Length; - bytesNeeded += MemoryMarshal.AsBytes(valueSpan).Length; - - return bytesNeeded; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddNewLineAndIndentation(Span buffer) - { - int offset = 0; - // \r\n versus \n, depending on OS - if (JsonWriter.s_newLineUtf8Length == 2) - buffer[offset++] = JsonConstants.CarriageReturn; - - buffer[offset++] = JsonConstants.LineFeed; - - int indent = _indent; - - while (indent-- >= 0) - { - buffer[offset++] = JsonConstants.Space; - buffer[offset++] = JsonConstants.Space; - } - return offset; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddNewLineAndIndentation(Span buffer) - { - int offset = 0; - // \r\n versus \n, depending on OS - if (JsonWriter.s_newLineUtf16Length == 2) - buffer[offset++] = (char)JsonConstants.CarriageReturn; - - buffer[offset++] = (char)JsonConstants.LineFeed; - - int indent = _indent; - - while (indent-- >= 0) - { - buffer[offset++] = (char)JsonConstants.Space; - buffer[offset++] = (char)JsonConstants.Space; - } - - return offset; - } - } - - //TODO: Move to a separate file - public static class JsonWriter - { - internal static readonly byte[] s_newLineUtf8 = Encoding.UTF8.GetBytes(Environment.NewLine); - internal static readonly int s_newLineUtf8Length = s_newLineUtf8.Length; - internal static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); - internal static readonly int s_newLineUtf16Length = s_newLineUtf16.Length; - - public static JsonWriter Create( - TBufferWriter bufferWriter, - bool isUtf8, - bool prettyPrint = false) where TBufferWriter : IBufferWriter - { - return new JsonWriter(BufferWriter.Create(bufferWriter), isUtf8, prettyPrint); - } - - public static JsonWriter Create( - BufferWriter bufferWriter, - bool isUtf8, - bool prettyPrint = false) where TBufferWriter : IBufferWriter - { - return new JsonWriter(bufferWriter, isUtf8, prettyPrint); - } } } diff --git a/tests/Benchmarks/System.IO.Pipelines/E2E.cs b/tests/Benchmarks/System.IO.Pipelines/E2E.cs index c1146f09367..0df98151a49 100644 --- a/tests/Benchmarks/System.IO.Pipelines/E2E.cs +++ b/tests/Benchmarks/System.IO.Pipelines/E2E.cs @@ -59,7 +59,7 @@ public void TechEmpowerJsonNoIO(int numberOfRequests, int concurrentConnections) formatter.Append("\r\n\r\n"); // write body - JsonWriter> writer = JsonWriter.Create(formatter, true, true); + JsonWriterUtf8> writer = JsonWriter.CreateUtf8(formatter, prettyPrint: 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 48428a57a0a..fe2d48ab7ee 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -102,7 +102,7 @@ private TextWriter GetWriter() private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter output, int[] data) { - JsonWriter json = JsonWriter.Create(output, true, formatted); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -132,7 +132,7 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatter output, int[] data) { - JsonWriter json = JsonWriter.Create(output, false, formatted); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -203,7 +203,7 @@ private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayFormatter output) { - JsonWriter json = JsonWriter.Create(output, true, formatted); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); @@ -213,7 +213,7 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayForm private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) { - JsonWriter json = JsonWriter.Create(output, false, formatted); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index 3c94b0f7668..69dfa8009dd 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -17,7 +17,7 @@ public class JsonWriterTests public void WriteJsonUtf8() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriter json = JsonWriter.Create(formatter, true, prettyPrint: false); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(formatter, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -25,7 +25,7 @@ public void WriteJsonUtf8() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = JsonWriter.Create(formatter, true, prettyPrint: true); + json = JsonWriter.CreateUtf8(formatter, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -37,7 +37,7 @@ public void WriteJsonUtf8() public void WriteJsonUtf16() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriter json = JsonWriter.Create(formatter, false, prettyPrint: false); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(formatter, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -45,7 +45,7 @@ public void WriteJsonUtf16() Assert.Equal(expected, str.Replace(" ", "")); formatter.Clear(); - json = JsonWriter.Create(formatter, false, prettyPrint: true); + json = JsonWriter.CreateUtf16(formatter, prettyPrint: true); Write(ref json); formatted = formatter.Formatted; @@ -53,8 +53,32 @@ public void WriteJsonUtf16() Assert.Equal(expected, str.Replace("\r\n", "").Replace("\n", "").Replace(" ", "")); } - static string expected = "{\"age\":30,\"first\":\"John\",\"last\":\"Smith\",\"phoneNumbers\":[\"425-000-1212\",\"425-000-1213\",null],\"address\":{\"street\":\"1MicrosoftWay\",\"city\":\"Redmond\",\"zip\":98052},\"values\":[425121,-425122,425123]}"; - static void Write(ref JsonWriter json) + static readonly string expected = "{\"age\":30,\"first\":\"John\",\"last\":\"Smith\",\"phoneNumbers\":[\"425-000-1212\",\"425-000-1213\",null],\"address\":{\"street\":\"1MicrosoftWay\",\"city\":\"Redmond\",\"zip\":98052},\"values\":[425121,-425122,425123]}"; + static void Write(ref JsonWriterUtf8 json) + { + json.WriteObjectStart(); + json.WriteAttribute("age", 30); + json.WriteAttribute("first", "John"); + json.WriteAttribute("last", "Smith"); + json.WriteArrayStart("phoneNumbers"); + json.WriteValue("425-000-1212"); + json.WriteValue("425-000-1213"); + json.WriteNull(); + json.WriteArrayEnd(); + json.WriteObjectStart("address"); + json.WriteAttribute("street", "1 Microsoft Way"); + json.WriteAttribute("city", "Redmond"); + json.WriteAttribute("zip", 98052); + json.WriteObjectEnd(); + json.WriteArrayStart("values"); + json.WriteValue(425121); + json.WriteValue(-425122); + json.WriteValue(425123); + json.WriteArrayEnd(); + json.WriteObjectEnd(); + json.Flush(); + } + static void Write(ref JsonWriterUtf16 json) { json.WriteObjectStart(); json.WriteAttribute("age", 30); @@ -87,7 +111,7 @@ public void WriteHelloWorldJsonUtf16(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: false); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriter jsonUtf16 = JsonWriter.Create(output, false, prettyPrint); + JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("message", "Hello, World!"); @@ -108,7 +132,7 @@ public void WriteHelloWorldJsonUtf8(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: true); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriter jsonUtf8 = JsonWriter.Create(output, true, prettyPrint); + JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("message", "Hello, World!"); @@ -131,7 +155,7 @@ public void WriteBasicJsonUtf16(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: false, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriter jsonUtf16 = JsonWriter.Create(output, false, prettyPrint); + JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("age", 42); @@ -175,7 +199,7 @@ public void WriteBasicJsonUtf8(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: true, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriter jsonUtf8 = JsonWriter.Create(output, true, prettyPrint); + JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("age", 42); From 79bdb15b0bf774a025002450980767bf9ebe80c2 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 17 Jun 2018 16:43:44 +0100 Subject: [PATCH 10/17] Add wrapper --- .../Shared/SampleServer.cs | 4 +- .../System/Text/Json/JsonWriter.cs | 108 +++++++++++++++++- .../System.Text.JsonLab/JsonWriterPerf.cs | 8 +- .../JsonWriterTests.cs | 16 +-- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/samples/LowAllocationWebServer/Shared/SampleServer.cs b/samples/LowAllocationWebServer/Shared/SampleServer.cs index 89ddb6ce433..5271a8a2f13 100644 --- a/samples/LowAllocationWebServer/Shared/SampleServer.cs +++ b/samples/LowAllocationWebServer/Shared/SampleServer.cs @@ -62,7 +62,7 @@ static void WriteResponseForGetJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); + JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < 5; i++) @@ -94,7 +94,7 @@ static void WriteResponseForPostJson(HttpRequest request, ReadOnlySequence response.AppendEoh(); // write response JSON - JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); + JsonWriterUtf8 jsonWriter = JsonWriter.CreateUtf8(response, prettyPrint: false); jsonWriter.WriteObjectStart(); jsonWriter.WriteArrayStart("values"); for (int i = 0; i < requestedCount; i++) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 26150dd42ab..542ddbd4a15 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -18,32 +18,45 @@ public static class JsonWriter public static JsonWriterUtf8 CreateUtf8( TBufferWriter bufferWriter, - bool prettyPrint = false) where TBufferWriter : IBufferWriter + bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter { return new JsonWriterUtf8(BufferWriter.Create(bufferWriter), prettyPrint); } - public static JsonWriterUtf8 Create( + public static JsonWriterUtf8 CreateUtf8( BufferWriter bufferWriter, - bool prettyPrint = false) where TBufferWriter : IBufferWriter + bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter { return new JsonWriterUtf8(bufferWriter, prettyPrint); } + public static JsonWriterUtf8 CreateUtf8( + IBufferWriter bufferWriter, + bool prettyPrint = false) + { + return new JsonWriterUtf8(BufferWriter.Create(new IBufferWriter(bufferWriter)), prettyPrint); + } + public static JsonWriterUtf16 CreateUtf16( TBufferWriter bufferWriter, - bool prettyPrint = false) where TBufferWriter : IBufferWriter + bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter { return new JsonWriterUtf16(BufferWriter.Create(bufferWriter), prettyPrint); } public static JsonWriterUtf16 CreateUtf16( BufferWriter bufferWriter, - bool prettyPrint = false) where TBufferWriter : IBufferWriter + bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter { return new JsonWriterUtf16(bufferWriter, prettyPrint); } + public static JsonWriterUtf16 CreateUtf16( + IBufferWriter bufferWriter, + bool prettyPrint = false) + { + return new JsonWriterUtf16(BufferWriter.Create(new IBufferWriter(bufferWriter)), prettyPrint); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int CountDigits(ulong value) @@ -101,4 +114,89 @@ internal static int CountDigits(ulong value) return digits; } } + + public ref struct JsonWriterUtf8 + { + JsonWriterUtf8 _jsonWriter; + + internal JsonWriterUtf8(BufferWriter bufferWriter, bool prettyPrint = false) + { + _jsonWriter = new JsonWriterUtf8(bufferWriter, prettyPrint); + } + + public void WriteArrayStart() => _jsonWriter.WriteArrayStart(); + public void WriteArrayStart(string name) => _jsonWriter.WriteArrayStart(name); + public void WriteArrayEnd() => _jsonWriter.WriteArrayEnd(); + public void WriteAttributeNull(string name) => _jsonWriter.WriteAttributeNull(name); + public void WriteNull() => _jsonWriter.WriteNull(); + public void WriteObjectStart() => _jsonWriter.WriteObjectStart(); + public void WriteObjectStart(string name) => _jsonWriter.WriteObjectStart(name); + public void WriteObjectEnd() => _jsonWriter.WriteObjectEnd(); + public void WriteAttribute(string name, string value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, bool value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, long value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, ulong value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, DateTime value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, DateTimeOffset value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, Guid value) => _jsonWriter.WriteAttribute(name, value); + public void WriteValue(string value) => _jsonWriter.WriteValue(value); + public void WriteValue(bool value) => _jsonWriter.WriteValue(value); + public void WriteValue(long value) => _jsonWriter.WriteValue(value); + public void WriteValue(ulong value) => _jsonWriter.WriteValue(value); + public void WriteValue(DateTime value) => _jsonWriter.WriteValue(value); + public void WriteValue(DateTimeOffset value) => _jsonWriter.WriteValue(value); + public void WriteValue(Guid value) => _jsonWriter.WriteValue(value); + public void Flush() => _jsonWriter.Flush(); + } + + + public ref struct JsonWriterUtf16 + { + JsonWriterUtf16 _jsonWriter; + + internal JsonWriterUtf16(BufferWriter bufferWriter, bool prettyPrint = false) + { + _jsonWriter = new JsonWriterUtf16(bufferWriter, prettyPrint); + } + + public void WriteArrayStart() => _jsonWriter.WriteArrayStart(); + public void WriteArrayStart(string name) => _jsonWriter.WriteArrayStart(name); + public void WriteArrayEnd() => _jsonWriter.WriteArrayEnd(); + public void WriteAttributeNull(string name) => _jsonWriter.WriteAttributeNull(name); + public void WriteNull() => _jsonWriter.WriteNull(); + public void WriteObjectStart() => _jsonWriter.WriteObjectStart(); + public void WriteObjectStart(string name) => _jsonWriter.WriteObjectStart(name); + public void WriteObjectEnd() => _jsonWriter.WriteObjectEnd(); + public void WriteAttribute(string name, string value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, bool value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, long value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, ulong value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, DateTime value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, DateTimeOffset value) => _jsonWriter.WriteAttribute(name, value); + public void WriteAttribute(string name, Guid value) => _jsonWriter.WriteAttribute(name, value); + public void WriteValue(string value) => _jsonWriter.WriteValue(value); + public void WriteValue(bool value) => _jsonWriter.WriteValue(value); + public void WriteValue(long value) => _jsonWriter.WriteValue(value); + public void WriteValue(ulong value) => _jsonWriter.WriteValue(value); + public void WriteValue(DateTime value) => _jsonWriter.WriteValue(value); + public void WriteValue(DateTimeOffset value) => _jsonWriter.WriteValue(value); + public void WriteValue(Guid value) => _jsonWriter.WriteValue(value); + public void Flush() => _jsonWriter.Flush(); + } + + internal struct IBufferWriter : IBufferWriter + { + public IBufferWriter _writer; + + public IBufferWriter(IBufferWriter writer) + { + _writer = writer; + } + + public void Advance(int count) => _writer.Advance(count); + + public Memory GetMemory(int sizeHint = 0) => _writer.GetMemory(sizeHint); + + public Span GetSpan(int sizeHint = 0) => _writer.GetSpan(sizeHint); + } } diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index fe2d48ab7ee..ee31357d091 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -102,7 +102,7 @@ private TextWriter GetWriter() private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter output, int[] data) { - JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -132,7 +132,7 @@ private static void WriterSystemTextJsonBasicUtf8(bool formatted, ArrayFormatter private static void WriterSystemTextJsonBasicUtf16(bool formatted, ArrayFormatter output, int[] data) { - JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); json.WriteObjectStart(); json.WriteAttribute("age", 42); @@ -203,7 +203,7 @@ private static void WriterNewtonsoftBasic(bool formatted, TextWriter writer, int private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayFormatter output) { - JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(output, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); @@ -213,7 +213,7 @@ private static void WriterSystemTextJsonHelloWorldUtf8(bool formatted, ArrayForm private static void WriterSystemTextJsonHelloWorldUtf16(bool formatted, ArrayFormatter output) { - JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(output, formatted); json.WriteObjectStart(); json.WriteAttribute("message", "Hello, World!"); diff --git a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs index 69dfa8009dd..f684fb7ff8b 100644 --- a/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs +++ b/tests/System.Text.JsonLab.Tests/JsonWriterTests.cs @@ -17,7 +17,7 @@ public class JsonWriterTests public void WriteJsonUtf8() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriterUtf8 json = JsonWriter.CreateUtf8(formatter, prettyPrint: false); + JsonWriterUtf8 json = JsonWriter.CreateUtf8(formatter, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -37,7 +37,7 @@ public void WriteJsonUtf8() public void WriteJsonUtf16() { var formatter = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriterUtf16 json = JsonWriter.CreateUtf16(formatter, prettyPrint: false); + JsonWriterUtf16 json = JsonWriter.CreateUtf16(formatter, prettyPrint: false); Write(ref json); var formatted = formatter.Formatted; @@ -54,7 +54,7 @@ public void WriteJsonUtf16() } static readonly string expected = "{\"age\":30,\"first\":\"John\",\"last\":\"Smith\",\"phoneNumbers\":[\"425-000-1212\",\"425-000-1213\",null],\"address\":{\"street\":\"1MicrosoftWay\",\"city\":\"Redmond\",\"zip\":98052},\"values\":[425121,-425122,425123]}"; - static void Write(ref JsonWriterUtf8 json) + static void Write(ref JsonWriterUtf8 json) { json.WriteObjectStart(); json.WriteAttribute("age", 30); @@ -78,7 +78,7 @@ static void Write(ref JsonWriterUtf8 json) json.WriteObjectEnd(); json.Flush(); } - static void Write(ref JsonWriterUtf16 json) + static void Write(ref JsonWriterUtf16 json) { json.WriteObjectStart(); json.WriteAttribute("age", 30); @@ -111,7 +111,7 @@ public void WriteHelloWorldJsonUtf16(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: false); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); + JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("message", "Hello, World!"); @@ -132,7 +132,7 @@ public void WriteHelloWorldJsonUtf8(bool prettyPrint) string expectedStr = GetHelloWorldExpectedString(prettyPrint, isUtf8: true); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); + JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("message", "Hello, World!"); @@ -155,7 +155,7 @@ public void WriteBasicJsonUtf16(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: false, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf16); - JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); + JsonWriterUtf16 jsonUtf16 = JsonWriter.CreateUtf16(output, prettyPrint); jsonUtf16.WriteObjectStart(); jsonUtf16.WriteAttribute("age", 42); @@ -199,7 +199,7 @@ public void WriteBasicJsonUtf8(bool prettyPrint) string expectedStr = GetExpectedString(prettyPrint, isUtf8: true, data); var output = new ArrayFormatter(1024, SymbolTable.InvariantUtf8); - JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); + JsonWriterUtf8 jsonUtf8 = JsonWriter.CreateUtf8(output, prettyPrint); jsonUtf8.WriteObjectStart(); jsonUtf8.WriteAttribute("age", 42); From 77e40987429154849cd401bd0e914f2fa1e62c8d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 17 Jun 2018 16:43:53 +0100 Subject: [PATCH 11/17] Fix bug --- src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs index 707b5edaa2b..ad2c2dcf373 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs @@ -851,7 +851,7 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS { JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); } - if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) + if (Encodings.Utf16.ToUtf8Length(valueSpan, out int bytesNeededValue) != OperationStatus.Done) { JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); } From ce52b2912301379db35a9ef1264340ba0d8fd05f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 18 Jun 2018 02:15:38 +0100 Subject: [PATCH 12/17] Mild Optimizations --- .../System/Text/Json/JsonThrowHelper.cs | 11 +++++ .../System/Text/Json/JsonWriter.Utf16.cs | 38 ++------------- .../System/Text/Json/JsonWriter.Utf8.cs | 48 ++++--------------- .../System/Text/Json/JsonWriter.cs | 38 +++++++++++++++ 4 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonThrowHelper.cs b/src/System.Text.JsonLab/System/Text/Json/JsonThrowHelper.cs index 342c803c1f6..2b70ce83331 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonThrowHelper.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonThrowHelper.cs @@ -19,6 +19,17 @@ private static ArgumentException GetArgumentException(string message) return new ArgumentException(message); } + public static void ThrowArgumentExceptionInvalidUtf8String() + { + throw GetArgumentExceptionInvalidUtf8String(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentException GetArgumentExceptionInvalidUtf8String() + { + return new ArgumentException("Invalid or incomplete UTF-8 string"); + } + public static void ThrowFormatException() { throw GetFormatException(); diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs index 737e1e0ea6d..7d84c25ef50 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs @@ -357,13 +357,8 @@ private void WriteAttribute(ReadOnlySpan nameSpanChar, int bytesNeeded) /// The string value that will be quoted within the JSON data. public void WriteValue(string value) { - ReadOnlySpan valueSpan = value.AsSpan(); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(char), 2); - WriteValue(valueSpan, bytesNeeded); - } - - private void WriteValue(ReadOnlySpan valueSpanChar, int bytesNeeded) - { + ReadOnlySpan valueSpanChar = value.AsSpan(); + int bytesNeeded = CalculateValueBytesNeeded(valueSpanChar, sizeof(char), 2); Span charBuffer = MemoryMarshal.Cast(EnsureBuffer(bytesNeeded)); int idx = 0; @@ -391,11 +386,7 @@ private void WriteValue(ReadOnlySpan valueSpanChar, int bytesNeeded) /// The signed integer value to be written to JSON data. public void WriteValue(long value) { - WriteValue(value, CalculateValueBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length)); - } - - private void WriteValue(long value, int bytesNeeded) - { + int bytesNeeded = CalculateValueBytesNeeded(sizeof(char), JsonWriter.s_newLineUtf16Length); bool insertNegationSign = false; if (value < 0) { @@ -419,30 +410,11 @@ private void WriteValue(long value, int bytesNeeded) if (insertNegationSign) charBuffer[idx++] = '-'; - WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); + JsonWriter.WriteDigitsUInt64D((ulong)value, charBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (char)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (char)('0' + value); - } - /// /// Write a unsigned integer value into the current array. /// @@ -689,7 +661,7 @@ private Span EnsureBuffer(int needed = 256) { _bufferWriter.Ensure(needed); Span buffer = _bufferWriter.Buffer; - if (buffer.Length < needed) + if ((uint)needed > (uint)buffer.Length) JsonThrowHelper.ThrowOutOfMemoryException(); return buffer; } diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs index ad2c2dcf373..2c47737b2f8 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs @@ -378,13 +378,8 @@ private void WriteAttribute(ReadOnlySpan nameSpanByte, int bytesNeeded) /// The string value that will be quoted within the JSON data. public void WriteValue(string value) { - ReadOnlySpan valueSpan = MemoryMarshal.AsBytes(value.AsSpan()); - int bytesNeeded = CalculateValueBytesNeeded(valueSpan, sizeof(byte), 2); - WriteValue(valueSpan, bytesNeeded); - } - - private void WriteValue(ReadOnlySpan valueSpanByte, int bytesNeeded) - { + ReadOnlySpan valueSpanByte = MemoryMarshal.AsBytes(value.AsSpan()); + int bytesNeeded = CalculateValueBytesNeeded(valueSpanByte, sizeof(byte), 2); Span byteBuffer = EnsureBuffer(bytesNeeded); int idx = 0; @@ -418,11 +413,7 @@ private void WriteValue(ReadOnlySpan valueSpanByte, int bytesNeeded) /// The signed integer value to be written to JSON data. public void WriteValue(long value) { - WriteValue(value, CalculateValueBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length)); - } - - private void WriteValue(long value, int bytesNeeded) - { + int bytesNeeded = CalculateValueBytesNeeded(sizeof(byte), JsonWriter.s_newLineUtf8Length); bool insertNegationSign = false; if (value < 0) { @@ -446,30 +437,11 @@ private void WriteValue(long value, int bytesNeeded) if (insertNegationSign) byteBuffer[idx++] = (byte)'-'; - WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); + JsonWriter.WriteDigitsUInt64D((ulong)value, byteBuffer.Slice(idx, digitCount)); _bufferWriter.Advance(bytesNeeded); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDigitsUInt64D(ulong value, Span buffer) - { - // We can mutate the 'value' parameter since it's a copy-by-value local. - // It'll be used to represent the value left over after each division by 10. - - Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); - - for (int i = buffer.Length - 1; i >= 1; i--) - { - ulong temp = '0' + value; - value /= 10; - buffer[i] = (byte)(temp - (value * 10)); - } - - Debug.Assert(value < 10); - buffer[0] = (byte)('0' + value); - } - /// /// Write a unsigned integer value into the current array. /// @@ -711,7 +683,7 @@ private Span EnsureBuffer(int needed = 256) { _bufferWriter.Ensure(needed); Span buffer = _bufferWriter.Buffer; - if (buffer.Length < needed) + if ((uint)needed > (uint)buffer.Length) JsonThrowHelper.ThrowOutOfMemoryException(); return buffer; } @@ -780,7 +752,7 @@ private int CalculateValueBytesNeeded(ReadOnlySpan span, int numBytes, int if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + JsonThrowHelper.ThrowArgumentExceptionInvalidUtf8String(); } bytesNeeded += bytesNeededValue; return bytesNeeded; @@ -803,7 +775,7 @@ private int CalculateBytesNeeded(ReadOnlySpan span, int numBytes, int extr if (Encodings.Utf16.ToUtf8Length(span, out int bytesNeededValue) != OperationStatus.Done) { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + JsonThrowHelper.ThrowArgumentExceptionInvalidUtf8String(); } bytesNeeded += bytesNeededValue; return bytesNeeded; @@ -826,7 +798,7 @@ private int CalculateStartAttributeBytesNeeded(ReadOnlySpan nameSpan, int if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededValue) != OperationStatus.Done) { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + JsonThrowHelper.ThrowArgumentExceptionInvalidUtf8String(); } bytesNeeded += bytesNeededValue; return bytesNeeded; @@ -849,11 +821,11 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS if (Encodings.Utf16.ToUtf8Length(nameSpan, out int bytesNeededName) != OperationStatus.Done) { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + JsonThrowHelper.ThrowArgumentExceptionInvalidUtf8String(); } if (Encodings.Utf16.ToUtf8Length(valueSpan, out int bytesNeededValue) != OperationStatus.Done) { - JsonThrowHelper.ThrowArgumentException("Invalid or incomplete UTF-8 string"); + JsonThrowHelper.ThrowArgumentExceptionInvalidUtf8String(); } bytesNeeded += bytesNeededName; bytesNeeded += bytesNeededValue; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index 542ddbd4a15..d09bf4345f7 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -113,6 +113,44 @@ internal static int CountDigits(ulong value) return digits; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (byte)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (byte)('0' + value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void WriteDigitsUInt64D(ulong value, Span buffer) + { + // We can mutate the 'value' parameter since it's a copy-by-value local. + // It'll be used to represent the value left over after each division by 10. + + Debug.Assert(JsonWriter.CountDigits(value) == buffer.Length); + + for (int i = buffer.Length - 1; i >= 1; i--) + { + ulong temp = '0' + value; + value /= 10; + buffer[i] = (char)(temp - (value * 10)); + } + + Debug.Assert(value < 10); + buffer[0] = (char)('0' + value); + } } public ref struct JsonWriterUtf8 From 9ba1474fc1c6f343b37e9de647beeee4b8c26a08 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 18 Jun 2018 02:47:57 +0100 Subject: [PATCH 13/17] Remove method --- .../System/Text/Json/JsonWriter.Utf16.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs index 7d84c25ef50..cbdedb9ebc4 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs @@ -794,26 +794,6 @@ private int CalculateAttributeBytesNeeded(ReadOnlySpan nameSpan, ReadOnlyS return bytesNeeded; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddNewLineAndIndentation(Span buffer) - { - int offset = 0; - // \r\n versus \n, depending on OS - if (JsonWriter.s_newLineUtf8Length == 2) - buffer[offset++] = JsonConstants.CarriageReturn; - - buffer[offset++] = JsonConstants.LineFeed; - - int indent = _indent; - - while (indent-- >= 0) - { - buffer[offset++] = JsonConstants.Space; - buffer[offset++] = JsonConstants.Space; - } - return offset; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddNewLineAndIndentation(Span buffer) { From e781ba36ba8d21615c56a991a3d7e9c196237c66 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 18 Jun 2018 20:56:12 +0100 Subject: [PATCH 14/17] Inline ctors --- .../System/Text/Json/JsonWriter.Utf16.cs | 2 ++ .../System/Text/Json/JsonWriter.Utf8.cs | 2 ++ src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs | 8 +++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs index cbdedb9ebc4..0068ae2edd7 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs @@ -23,6 +23,8 @@ namespace System.Text.JsonLab /// /// An instance of used for writing bytes to an output channel. /// Specifies whether to add whitespace to the output text for user readability. + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public JsonWriterUtf16(BufferWriter bufferWriter, bool prettyPrint = false) { _bufferWriter = bufferWriter; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs index 2c47737b2f8..5eab897374c 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs @@ -23,6 +23,8 @@ namespace System.Text.JsonLab /// /// An instance of used for writing bytes to an output channel. /// Specifies whether to add whitespace to the output text for user readability. + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public JsonWriterUtf8(BufferWriter bufferWriter, bool prettyPrint = false) { _bufferWriter = bufferWriter; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index d09bf4345f7..f5cbe862f84 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -5,7 +5,6 @@ using System.Buffers.Writer; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Text.JsonLab { @@ -16,6 +15,7 @@ public static class JsonWriter internal static readonly char[] s_newLineUtf16 = Environment.NewLine.ToCharArray(); internal static readonly int s_newLineUtf16Length = s_newLineUtf16.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf8 CreateUtf8( TBufferWriter bufferWriter, bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter @@ -23,6 +23,7 @@ public static JsonWriterUtf8 CreateUtf8( return new JsonWriterUtf8(BufferWriter.Create(bufferWriter), prettyPrint); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf8 CreateUtf8( BufferWriter bufferWriter, bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter @@ -30,6 +31,7 @@ public static JsonWriterUtf8 CreateUtf8( return new JsonWriterUtf8(bufferWriter, prettyPrint); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf8 CreateUtf8( IBufferWriter bufferWriter, bool prettyPrint = false) @@ -37,6 +39,7 @@ public static JsonWriterUtf8 CreateUtf8( return new JsonWriterUtf8(BufferWriter.Create(new IBufferWriter(bufferWriter)), prettyPrint); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf16 CreateUtf16( TBufferWriter bufferWriter, bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter @@ -44,6 +47,7 @@ public static JsonWriterUtf16 CreateUtf16( return new JsonWriterUtf16(BufferWriter.Create(bufferWriter), prettyPrint); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf16 CreateUtf16( BufferWriter bufferWriter, bool prettyPrint = false) where TBufferWriter : struct, IBufferWriter @@ -51,6 +55,7 @@ public static JsonWriterUtf16 CreateUtf16( return new JsonWriterUtf16(bufferWriter, prettyPrint); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsonWriterUtf16 CreateUtf16( IBufferWriter bufferWriter, bool prettyPrint = false) @@ -226,6 +231,7 @@ internal struct IBufferWriter : IBufferWriter { public IBufferWriter _writer; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IBufferWriter(IBufferWriter writer) { _writer = writer; From a2008a3572f1d576d36d2a403765f8e4d213027d Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 18 Jun 2018 21:07:18 +0100 Subject: [PATCH 15/17] Use StructLayout(LayoutKind.Auto --- .../System/Buffers/Reader/BufferReader.cs | 2 ++ .../System/Buffers/Writer/BufferWriter.cs | 2 ++ .../System/Buffers/Writer/BufferWriterT.cs | 2 ++ .../System/Buffers/Writer/TransformationFormat.cs | 2 ++ src/System.Text.JsonLab/System/Text/Json/JsonParseObject.cs | 2 ++ src/System.Text.JsonLab/System/Text/Json/JsonParser.cs | 2 ++ src/System.Text.JsonLab/System/Text/Json/JsonReader.cs | 2 ++ src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs | 1 + src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs | 1 + src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs | 4 +++- 10 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader.cs index c0dc2b1aec0..3f25ec1cc4d 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Reader/BufferReader.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Buffers.Reader { + [StructLayout(LayoutKind.Auto)] public ref struct BufferReader { private SequencePosition _currentSequencePosition; diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriter.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriter.cs index 116bd3564ea..dc835dc7b8c 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriter.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriter.cs @@ -4,9 +4,11 @@ using System.Buffers.Text; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Buffers.Writer { + [StructLayout(LayoutKind.Auto)] public ref partial struct BufferWriter { private Span _buffer; diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs index 0a73b0de2f0..e35a32a5954 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Buffers.Writer { + [StructLayout(LayoutKind.Auto)] public ref partial struct BufferWriter where T : IBufferWriter { private T _output; diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/TransformationFormat.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/TransformationFormat.cs index 7b678671063..8507b899785 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/TransformationFormat.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/TransformationFormat.cs @@ -4,9 +4,11 @@ using System.Buffers.Operations; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Buffers.Writer { + [StructLayout(LayoutKind.Auto)] public readonly struct TransformationFormat { private readonly IBufferTransformation _first; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonParseObject.cs b/src/System.Text.JsonLab/System/Text/Json/JsonParseObject.cs index 70ef85fa155..e1c6ad74967 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonParseObject.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonParseObject.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Buffers.Text; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text.Utf8; using static System.Buffers.Binary.BinaryPrimitives; @@ -11,6 +12,7 @@ namespace System.Text.JsonLab { + [StructLayout(LayoutKind.Auto)] public ref struct JsonObject { private MemoryPool _pool; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonParser.cs b/src/System.Text.JsonLab/System/Text/Json/JsonParser.cs index d444c6afc8c..910cb64e86f 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonParser.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonParser.cs @@ -38,6 +38,7 @@ static DbRow() } } + [StructLayout(LayoutKind.Auto)] internal ref struct TwoStacks { Span _span; @@ -112,6 +113,7 @@ internal void Resize(Span newStackMemory) } } + [StructLayout(LayoutKind.Auto)] internal ref struct JsonParser { private Span _db; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonReader.cs b/src/System.Text.JsonLab/System/Text/Json/JsonReader.cs index a3981f30505..f511bf6af04 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonReader.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonReader.cs @@ -7,6 +7,8 @@ namespace System.Text.JsonLab { + + [StructLayout(LayoutKind.Auto)] public ref struct JsonReader { // We are using a ulong to represent our nested state, so we can only go 64 levels deep. diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs index 0068ae2edd7..d3097068c3c 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf16.cs @@ -10,6 +10,7 @@ namespace System.Text.JsonLab { + [StructLayout(LayoutKind.Auto)] public ref struct JsonWriterUtf16 where TBufferWriter : IBufferWriter { private readonly bool _prettyPrint; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs index 5eab897374c..5bf7091f2c1 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.Utf8.cs @@ -10,6 +10,7 @@ namespace System.Text.JsonLab { + [StructLayout(LayoutKind.Auto)] public ref struct JsonWriterUtf8 where TBufferWriter : IBufferWriter { private readonly bool _prettyPrint; diff --git a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs index f5cbe862f84..98b8bce0506 100644 --- a/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs +++ b/src/System.Text.JsonLab/System/Text/Json/JsonWriter.cs @@ -5,6 +5,7 @@ using System.Buffers.Writer; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Text.JsonLab { @@ -158,6 +159,7 @@ internal static void WriteDigitsUInt64D(ulong value, Span buffer) } } + [StructLayout(LayoutKind.Auto)] public ref struct JsonWriterUtf8 { JsonWriterUtf8 _jsonWriter; @@ -192,7 +194,7 @@ internal JsonWriterUtf8(BufferWriter bufferWriter, bool prettyPri public void Flush() => _jsonWriter.Flush(); } - + [StructLayout(LayoutKind.Auto)] public ref struct JsonWriterUtf16 { JsonWriterUtf16 _jsonWriter; From abbb65abc689e31d56a3d22882d0d950f40882f0 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 19 Jun 2018 01:32:10 +0100 Subject: [PATCH 16/17] Inline .ctor --- .../System/Buffers/Writer/BufferWriterT.cs | 1 + src/System.Text.JsonLab/System.Text.JsonLab.csproj | 2 ++ tests/Benchmarks/Benchmarks.csproj | 2 ++ tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs index e35a32a5954..251d9e9c682 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs @@ -16,6 +16,7 @@ namespace System.Buffers.Writer private static readonly byte[] s_newLine = new byte[] { (byte)'\r', (byte)'\n' }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferWriter(T output) { _buffered = 0; diff --git a/src/System.Text.JsonLab/System.Text.JsonLab.csproj b/src/System.Text.JsonLab/System.Text.JsonLab.csproj index e70caf4e5c0..99e0e539a75 100644 --- a/src/System.Text.JsonLab/System.Text.JsonLab.csproj +++ b/src/System.Text.JsonLab/System.Text.JsonLab.csproj @@ -7,6 +7,8 @@ .NET json non-allocating corefxlab http://go.microsoft.com/fwlink/?linkid=833199 false + pdbonly + true diff --git a/tests/Benchmarks/Benchmarks.csproj b/tests/Benchmarks/Benchmarks.csproj index 8db75e808b7..36ee2711ec1 100644 --- a/tests/Benchmarks/Benchmarks.csproj +++ b/tests/Benchmarks/Benchmarks.csproj @@ -5,6 +5,8 @@ Exe Microsoft Corporation, All rights reserved $(NoWarn);CS0618 + pdbonly + true diff --git a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs index ee31357d091..2cb5d1f88c9 100644 --- a/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs +++ b/tests/Benchmarks/System.Text.JsonLab/JsonWriterPerf.cs @@ -5,9 +5,13 @@ using System.Buffers.Text; using System.IO; using System.Text.Formatting; +using BenchmarkDotNet.Diagnostics.Windows; +using BenchmarkDotNet.Diagnostics.Windows.Configs; namespace System.Text.JsonLab.Benchmarks { + [DisassemblyDiagnoser(printAsm: true, printSource: true)] + [InliningDiagnoser()] [MemoryDiagnoser] public class JsonWriterPerf { From cd3964d37931c613d06fe5423a9185f7980766cf Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 19 Jun 2018 19:47:07 +0100 Subject: [PATCH 17/17] Remove additional ROSpan field from BufferWriter --- .../System/Buffers/Writer/BufferWriterT.cs | 6 +++--- .../System/Buffers/Writer/BufferWriterT_strings.cs | 12 ++++++++++++ .../BasicUnitTests.cs | 12 ++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs index 251d9e9c682..2570937e289 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System.Buffers.Writer { @@ -14,7 +15,7 @@ namespace System.Buffers.Writer private Span _span; private int _buffered; - private static readonly byte[] s_newLine = new byte[] { (byte)'\r', (byte)'\n' }; + private static readonly byte[] s_newLine = Encoding.UTF8.GetBytes(Environment.NewLine); [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferWriter(T output) @@ -22,10 +23,9 @@ public BufferWriter(T output) _buffered = 0; _output = output; _span = output.GetSpan(); - NewLine = s_newLine; } - public ReadOnlySpan NewLine { get; set; } + private static ReadOnlySpan NewLine => new ReadOnlySpan(s_newLine); public Span Buffer => _span; diff --git a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT_strings.cs b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT_strings.cs index d5c90cfa89f..714b258b3e1 100644 --- a/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT_strings.cs +++ b/src/System.Buffers.ReaderWriter/System/Buffers/Writer/BufferWriterT_strings.cs @@ -44,6 +44,12 @@ public void WriteLine(string value) Write(NewLine); } + public void WriteLine(string value, ReadOnlySpan newLine) + { + Write(value); + Write(newLine); + } + //public void WriteLine(string value, TransformationFormat format); public void Write(Utf8String value) => Write(value.Bytes); @@ -56,6 +62,12 @@ public void WriteLine(Utf8String value) Write(NewLine); } + public void WriteLine(Utf8String value, ReadOnlySpan newLine) + { + Write(value.Bytes); + Write(newLine); + } + //public void WriteLine(Utf8String value, TransformationFormat format); [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/System.Buffers.ReaderWriter.Tests/BasicUnitTests.cs b/tests/System.Buffers.ReaderWriter.Tests/BasicUnitTests.cs index 31439ef42a5..b6264d7d8ff 100644 --- a/tests/System.Buffers.ReaderWriter.Tests/BasicUnitTests.cs +++ b/tests/System.Buffers.ReaderWriter.Tests/BasicUnitTests.cs @@ -76,9 +76,9 @@ public void WriteLineString() { _sink.Reset(); var writer = BufferWriter.Create(_sink); - writer.NewLine = new byte[] { (byte)'X', (byte)'Y' }; - writer.WriteLine("hello world"); - writer.WriteLine("!"); + var newLine = new ReadOnlySpan(new byte[] { (byte)'X', (byte)'Y' }); + writer.WriteLine("hello world", newLine); + writer.WriteLine("!", newLine); writer.Flush(); var result = _sink.ToString(); @@ -90,9 +90,9 @@ public void WriteLineUtf8String() { _sink.Reset(); var writer = BufferWriter.Create(_sink); - writer.NewLine = new byte[] { (byte)'X', (byte)'Y' }; - writer.WriteLine((Utf8String)"hello world"); - writer.WriteLine((Utf8String)"!"); + var newLine = new ReadOnlySpan(new byte[] { (byte)'X', (byte)'Y' }); + writer.WriteLine((Utf8String)"hello world", newLine); + writer.WriteLine((Utf8String)"!", newLine); writer.Flush(); var result = _sink.ToString();