From 8c5d6405d749d9d7f73d5b04506205f4de0f37fc Mon Sep 17 00:00:00 2001 From: John Gathogo Date: Thu, 20 May 2021 20:42:24 +0300 Subject: [PATCH] Consistently use CharArrayPool in JsonWriter to cut cost of initializing char arrays --- src/Microsoft.OData.Core/Json/JsonWriter.cs | 8 ++--- .../Json/JsonWriterAsync.cs | 12 +++---- .../Json/JsonWriterAsyncTests.cs | 36 +++++++++++++++++-- .../Json/JsonWriterTests.cs | 32 ++++++++++++++++- .../Json/MockCharArrayPool.cs | 31 ++++++++++++++++ 5 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 test/FunctionalTests/Microsoft.OData.Core.Tests/Json/MockCharArrayPool.cs diff --git a/src/Microsoft.OData.Core/Json/JsonWriter.cs b/src/Microsoft.OData.Core/Json/JsonWriter.cs index 6cd5ed2ca8..0dc219ae61 100644 --- a/src/Microsoft.OData.Core/Json/JsonWriter.cs +++ b/src/Microsoft.OData.Core/Json/JsonWriter.cs @@ -215,7 +215,7 @@ public void WriteName(string name) currentScope.ObjectCount++; - JsonValueUtils.WriteEscapedJsonString(this.writer, name, this.stringEscapeOption, this.wrappedBuffer); + JsonValueUtils.WriteEscapedJsonString(this.writer, name, this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool); this.writer.Write(JsonConstants.NameValueSeparator); } @@ -280,7 +280,7 @@ public void WriteValue(long value) if (isIeee754Compatible) { JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), - this.stringEscapeOption, this.wrappedBuffer); + this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool); } else { @@ -320,7 +320,7 @@ public void WriteValue(decimal value) if (isIeee754Compatible) { JsonValueUtils.WriteValue(this.writer, value.ToString(CultureInfo.InvariantCulture), - this.stringEscapeOption, this.wrappedBuffer); + this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool); } else { @@ -395,7 +395,7 @@ public void WriteValue(sbyte value) public void WriteValue(string value) { this.WriteValueSeparator(); - JsonValueUtils.WriteValue(this.writer, value, this.stringEscapeOption, this.wrappedBuffer); + JsonValueUtils.WriteValue(this.writer, value, this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool); } /// diff --git a/src/Microsoft.OData.Core/Json/JsonWriterAsync.cs b/src/Microsoft.OData.Core/Json/JsonWriterAsync.cs index d0abdd4149..6df34d76bc 100644 --- a/src/Microsoft.OData.Core/Json/JsonWriterAsync.cs +++ b/src/Microsoft.OData.Core/Json/JsonWriterAsync.cs @@ -100,7 +100,7 @@ public async Task WriteNameAsync(string name) currentScope.ObjectCount++; - await this.writer.WriteEscapedJsonStringAsync(name, this.stringEscapeOption, this.wrappedBuffer) + await this.writer.WriteEscapedJsonStringAsync(name, this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool) .ConfigureAwait(false); await this.writer.WriteAsync(JsonConstants.NameValueSeparator).ConfigureAwait(false); } @@ -148,7 +148,7 @@ public async Task WriteValueAsync(long value) if (isIeee754Compatible) { await this.writer.WriteValueAsync(value.ToString(CultureInfo.InvariantCulture), - this.stringEscapeOption, this.wrappedBuffer).ConfigureAwait(false); + this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool).ConfigureAwait(false); } else { @@ -179,7 +179,7 @@ public async Task WriteValueAsync(decimal value) if (isIeee754Compatible) { await this.writer.WriteValueAsync(value.ToString(CultureInfo.InvariantCulture), - this.stringEscapeOption, this.wrappedBuffer).ConfigureAwait(false); + this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool).ConfigureAwait(false); } else { @@ -234,7 +234,7 @@ public async Task WriteValueAsync(sbyte value) public async Task WriteValueAsync(string value) { await this.WriteValueSeparatorAsync().ConfigureAwait(false); - await this.writer.WriteValueAsync(value, this.stringEscapeOption, this.wrappedBuffer) + await this.writer.WriteValueAsync(value, this.stringEscapeOption, this.wrappedBuffer, this.ArrayPool) .ConfigureAwait(false); } @@ -242,7 +242,7 @@ await this.writer.WriteValueAsync(value, this.stringEscapeOption, this.wrappedBu public async Task WriteValueAsync(byte[] value) { await this.WriteValueSeparatorAsync().ConfigureAwait(false); - await this.writer.WriteValueAsync(value, this.wrappedBuffer, ArrayPool).ConfigureAwait(false); + await this.writer.WriteValueAsync(value, this.wrappedBuffer, this.ArrayPool).ConfigureAwait(false); } /// @@ -270,7 +270,7 @@ public async Task StartStreamValueScopeAsync() // ODL supports net45 and netstandard1.1 (in addition to .netstandard2.0) // This makes it complicated to implement IAsyncDisposable in ODataBinaryStreamWriter // TODO: Can the returned stream be safely used asynchronously? - this.binaryValueStream = new ODataBinaryStreamWriter(writer, this.wrappedBuffer, ArrayPool); + this.binaryValueStream = new ODataBinaryStreamWriter(writer, this.wrappedBuffer, this.ArrayPool); return this.binaryValueStream; } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterAsyncTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterAsyncTests.cs index 789fd02501..dd37ddece0 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterAsyncTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterAsyncTests.cs @@ -5,13 +5,13 @@ //--------------------------------------------------------------------- using System; +using System.Collections.Generic; using System.IO; using System.Text; -using Microsoft.OData.Json; +using System.Threading.Tasks; using Microsoft.OData.Edm; +using Microsoft.OData.Json; using Xunit; -using System.Threading.Tasks; -using System.Collections.Generic; namespace Microsoft.OData.Tests.Json { @@ -359,5 +359,35 @@ private async Task VerifyWriterPrimitiveValueWithIeee754CompatibleAsync(T par } #endregion + + [Fact] + public Task WriteNameAsyncUsesProvidedCharArrayPool() + { + // Note: CharArrayPool is used if string has special chars + // This test is mostly theoretical since special characters are not allowed in names + return SetupJsonWriterRunTestAndVerifyRentAsync( + (jsonWriter) => jsonWriter.WriteNameAsync("foo\tbar")); + } + + [Fact] + public Task WriteStringValueUsesProvidedCharArrayPool() + { + return SetupJsonWriterRunTestAndVerifyRentAsync( + (jsonWriter) => jsonWriter.WriteValueAsync("foo\tbar")); + } + + private async Task SetupJsonWriterRunTestAndVerifyRentAsync(Func func) + { + var jsonWriter = new JsonWriter(new StringWriter(builder), isIeee754Compatible: true); + bool rentVerified = false; + + Action rentVerifier = (minSize) => { rentVerified = true; }; + jsonWriter.ArrayPool = new MockCharArrayPool { RentVerifier = rentVerifier }; + + await jsonWriter.StartObjectScopeAsync(); + await func(jsonWriter); + + Assert.True(rentVerified); + } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterTests.cs index e8cd02bac6..baacb77e19 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/JsonWriterTests.cs @@ -7,8 +7,8 @@ using System; using System.IO; using System.Text; -using Microsoft.OData.Json; using Microsoft.OData.Edm; +using Microsoft.OData.Json; using Xunit; namespace Microsoft.OData.Tests.Json @@ -174,5 +174,35 @@ private void VerifyWriterPrimitiveValueWithIeee754Compatible(T parameter, str } #endregion + + [Fact] + public void WriteNameUsesProvidedCharArrayPool() + { + // Note: CharArrayPool is used if string has special chars + // This test is mostly theoretical since special characters are not allowed in names + SetupJsonWriterRunTestAndVerifyRent( + (jsonWriter) => jsonWriter.WriteName("foo\tbar")); + } + + [Fact] + public void WriteStringValueUsesProvidedCharArrayPool() + { + SetupJsonWriterRunTestAndVerifyRent( + (jsonWriter) => jsonWriter.WriteValue("foo\tbar")); + } + + private void SetupJsonWriterRunTestAndVerifyRent(Action action) + { + var jsonWriter = new JsonWriter(new StringWriter(builder), isIeee754Compatible: true); + bool rentVerified = false; + + Action rentVerifier = (minSize) => { rentVerified = true; }; + jsonWriter.ArrayPool = new MockCharArrayPool { RentVerifier = rentVerifier }; + + jsonWriter.StartObjectScope(); + action(jsonWriter); + + Assert.True(rentVerified); + } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/MockCharArrayPool.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/MockCharArrayPool.cs new file mode 100644 index 0000000000..a095cf7965 --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Json/MockCharArrayPool.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using Microsoft.OData.Buffers; + +namespace Microsoft.OData.Tests.Json +{ + public class MockCharArrayPool : ICharArrayPool + { + public Action RentVerifier; + public Action ReturnVerifier; + + public char[] Rent(int minSize) + { + var charArray = new char[minSize]; + RentVerifier(minSize); + + return charArray; + } + + public void Return(char[] array) + { + ReturnVerifier(array); + // Do nothing else + } + } +}