From 27998b5dfb07f14cb613029b12845dd059e5f420 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Wed, 14 Feb 2024 18:55:01 +0100 Subject: [PATCH 01/19] updated EscapeString --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 38 ++++++++++++++++++- .../Types/Encoders/JsonEncoderBenchmarks.cs | 20 ++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index b4b73d4867..2e055c3c63 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -492,7 +492,7 @@ public void PopNamespace() private static readonly char[] m_specialChars = new char[] { '"', '\\', '\n', '\r', '\t', '\b', '\f', }; private static readonly char[] m_substitution = new char[] { '"', '\\', 'n', 'r', 't', 'b', 'f' }; - private void EscapeString(string value) + private void EscapeString1(string value) { foreach (char ch in value) { @@ -523,6 +523,42 @@ private void EscapeString(string value) } } + private static readonly Dictionary m_substitution1 = new Dictionary + { + { '"', "\\\"" }, + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '\b', "\\b" }, + { '\f', "\\f" } + }; + private void EscapeString(string value) + { + StringBuilder m_stringBuilder = new StringBuilder(value.Length * 2); + + foreach (char ch in value) + { + // chekc if ch is present in the dictionary + if (m_substitution1.TryGetValue(ch, out string escapeSequence)) + { + m_stringBuilder.Append(escapeSequence); + } + else if (ch < 32) + { + m_stringBuilder.Append("\\u"); + m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + continue; + } + else + { + m_stringBuilder.Append(ch); + } + } + + m_writer.Write(m_stringBuilder.ToString()); + } + private void WriteSimpleField(string fieldName, string value, bool quotes) { if (!String.IsNullOrEmpty(fieldName)) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index b7bd66fa57..718367c8ff 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -146,6 +146,24 @@ public void JsonEncoder_Constructor_Streamwriter_Reflection2() } } + /// + /// Benchmark test for EscapeString. + /// + [Benchmark] + [Test] + public void JsonEncoderEscapeString() + { + using (var jsonEncoder = new JsonEncoder(m_context, false)) + { + jsonEncoder.WriteString("String", m_testString); + var result = jsonEncoder.CloseAndReturnText(); + + Assert.NotNull(result); + Assert.AreEqual(1, result.Split(new string[] { m_testString }, StringSplitOptions.None).Length); + + } + } + #region Private Methods private void TestEncoding(IEncoder encoder) { @@ -254,6 +272,8 @@ public void GlobalCleanup() private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; private BufferManager m_bufferManager; private ArraySegmentStream m_arraySegmentStream; + // private string m_testString = "Hello\tWorld\nThis is a \"test\" string with \\backslashes and \r\nnewlines\f"; + private string m_testString = "This is a test string with special characters: \" \n \r \t \b \f \\ and some control characters: \0 \x01 \x02 \x03 \x04"; #endregion } } From 58f4dff9c2a81140309adb548cdb5b012bee19be Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Fri, 16 Feb 2024 14:38:41 +0100 Subject: [PATCH 02/19] fixed memory issue in benchmark test --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 55 ++--- .../Types/Encoders/JsonEncoderBenchmarks.cs | 199 ++++++++++++++++-- 2 files changed, 193 insertions(+), 61 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 8d4e39e08a..a570b876b3 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -498,58 +498,32 @@ public void PopNamespace() m_namespaces.Pop(); } - private static readonly char[] m_specialChars = new char[] { s_quotation, s_backslash, '\n', '\r', '\t', '\b', '\f', }; - private static readonly char[] m_substitution = new char[] { s_quotation, s_backslash, 'n', 'r', 't', 'b', 'f' }; - - private void EscapeString1(string value) - { - foreach (char ch in value) - { - bool found = false; - - for (int ii = 0; ii < m_specialChars.Length; ii++) - { - if (m_specialChars[ii] == ch) - { - m_writer.Write(s_backslash); - m_writer.Write(m_substitution[ii]); - found = true; - break; - } - } - - if (!found) - { - if (ch < 32) - { - m_writer.Write("\\u"); - m_writer.Write("{0:X4}", (int)ch); - continue; - } - - m_writer.Write(ch); - } - } - } - - private static readonly Dictionary m_substitution1 = new Dictionary + private static readonly Dictionary m_substitution = new Dictionary { - { '"', "\\\"" }, - { '\\', "\\\\" }, + { s_quotation, "\\\"" }, + { s_backslash, "\\\\" }, { '\n', "\\n" }, { '\r', "\\r" }, { '\t', "\\t" }, { '\b', "\\b" }, { '\f', "\\f" } }; + + /// + /// Escapes a string and writes it to the stream. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { StringBuilder m_stringBuilder = new StringBuilder(value.Length * 2); + Dictionary substitution = new Dictionary(m_substitution); + foreach (char ch in value) { - // chekc if ch is present in the dictionary - if (m_substitution1.TryGetValue(ch, out string escapeSequence)) + // Check if ch is present in the dictionary + if (substitution.TryGetValue(ch, out string escapeSequence)) { m_stringBuilder.Append(escapeSequence); } @@ -557,7 +531,6 @@ private void EscapeString(string value) { m_stringBuilder.Append("\\u"); m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); - continue; } else { @@ -565,7 +538,7 @@ private void EscapeString(string value) } } - m_writer.Write(m_stringBuilder.ToString()); + m_writer.Write(m_stringBuilder); } private void WriteSimpleField(string fieldName, string value, bool quotes) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 4bb980e308..03bfddad88 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -28,12 +28,19 @@ * ======================================================================*/ using System; +using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; +using System.Xml; using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; +using FastSerialization; +using Microsoft.Extensions.Logging; +using Microsoft.IO; using NUnit.Framework; +using NUnit.Framework.Constraints; using Opc.Ua.Bindings; namespace Opc.Ua.Core.Tests.Types.Encoders @@ -147,24 +154,6 @@ public void JsonEncoderConstructorStreamwriterReflection2() } } - /// - /// Benchmark test for EscapeString. - /// - [Benchmark] - [Test] - public void JsonEncoderEscapeString() - { - using (var jsonEncoder = new JsonEncoder(m_context, false)) - { - jsonEncoder.WriteString("String", m_testString); - var result = jsonEncoder.CloseAndReturnText(); - - Assert.NotNull(result); - Assert.AreEqual(1, result.Split(new string[] { m_testString }, StringSplitOptions.None).Length); - - } - } - #region Private Methods private void TestEncoding(IEncoder encoder) { @@ -273,8 +262,6 @@ public void GlobalCleanup() private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; private BufferManager m_bufferManager; private ArraySegmentStream m_arraySegmentStream; - // private string m_testString = "Hello\tWorld\nThis is a \"test\" string with \\backslashes and \r\nnewlines\f"; - private string m_testString = "This is a test string with special characters: \" \n \r \t \b \f \\ and some control characters: \0 \x01 \x02 \x03 \x04"; #endregion } @@ -351,4 +338,176 @@ public void GlobalCleanup() #endregion } + + [TestFixture, Category("JsonEncoder")] + [SetCulture("en-us"), SetUICulture("en-us")] + [NonParallelizable] + [MemoryDiagnoser] + [DisassemblyDiagnoser(printSource: true)] + public class JsonEncoderEscapeStringBenchmark + { + + [Params(1,2,3,4)] + public int TestStringIndex { get; set; } = 4; + + [Test] + [Benchmark] + public void EscapeStringBenchmark1() + { + m_memoryStream.SetLength(0); + m_memoryStream.Position = 0; + EscapedStringToStream(m_testString); + m_streamWriter?.Flush(); + } + + [Test] + [Benchmark] + public void EscapeStringBenchmark2() + { + m_memoryStream.SetLength(0); + m_memoryStream.Position = 0; + EscapeString(m_testString); + m_streamWriter?.Flush(); + } + + #region Test Setup + [OneTimeSetUp] + public void OneTimeSetUp() + { + m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); + m_memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager);// new MemoryStream(); + m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); + + m_testString = "Test string ascii, special characters \n \b and control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + var result = Encoding.UTF8.GetString(m_memoryStream.ToArray()); + Assert.NotNull(result); + + m_streamWriter?.Dispose(); + m_streamWriter = null; + m_memoryStream?.Dispose(); + m_memoryStream = null; + m_recyclableMemoryStream?.Dispose(); + m_recyclableMemoryStream = null; + m_memoryManager = null; + } + #endregion + + #region Benchmark Setup + /// + /// Set up some variables for benchmarks. + /// + [GlobalSetup] + public void GlobalSetup() + { + m_memoryStream = new MemoryStream(); + m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); + + // for validating benchmark tests + switch (TestStringIndex) + { + case 1: m_testString = "Ascii characters 12345"; break; + case 2: m_testString = "\" \n \r \t \b \f \\"; break; + case 3: m_testString = "\0 \x01 \x02 \x03 \x04"; break; + default: m_testString = "Ascii characters , special characters \n \b & control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; break; + } + } + + [GlobalCleanup] + public void GlobalCleanup() + { + m_streamWriter?.Dispose(); + m_streamWriter = null; + m_memoryStream?.Dispose(); + m_memoryStream = null; + m_recyclableMemoryStream?.Dispose(); + m_recyclableMemoryStream = null; + m_memoryManager = null; + } + #endregion + + #region Private Methods + private void EscapedStringToStream(string value) + { + foreach (char ch in value) + { + bool found = false; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + m_streamWriter.Write('\\'); + m_streamWriter.Write(m_substitution[ii]); + found = true; + break; + } + } + + if (!found) + { + if (ch < 32) + { + m_streamWriter.Write("\\u"); + m_streamWriter.Write("{0:X4}", (int)ch); + continue; + } + + m_streamWriter.Write(ch); + } + } + } + + private static readonly Dictionary m_replace = new Dictionary + { + { '\"', "\\\"" }, + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '\b', "\\b" }, + { '\f', "\\f" } + }; + private void EscapeString(string value) + { + StringBuilder m_stringBuilder = new StringBuilder(value.Length * 2); + + Dictionary substitution = new Dictionary(m_replace); + + foreach (char ch in value) + { + // Check if ch is present in the dictionary + if (substitution.TryGetValue(ch, out string escapeSequence)) + { + m_stringBuilder.Append(escapeSequence); + } + else if (ch < 32) + { + m_stringBuilder.Append("\\u"); + m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + else + { + m_stringBuilder.Append(ch); + } + } + m_streamWriter.Write(m_stringBuilder); + } + #endregion + + #region Private Fields + private static string m_testString; + private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; + private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; + private MemoryStream m_memoryStream; + private StreamWriter m_streamWriter; + private int m_streamSize = 2048; + private static readonly char[] m_specialChars = new char[] { '\"', '\\', '\n', '\r', '\t', '\b', '\f', }; + private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + #endregion + } } From 4270e1f9ea9d38c9a4f494d9ee551a5932240a88 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Fri, 16 Feb 2024 14:43:14 +0100 Subject: [PATCH 03/19] updated variable name --- .../Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 03bfddad88..5a6ba30ae8 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -348,7 +348,7 @@ public class JsonEncoderEscapeStringBenchmark { [Params(1,2,3,4)] - public int TestStringIndex { get; set; } = 4; + public int StringVariantIndex { get; set; } = 4; [Test] [Benchmark] @@ -408,7 +408,7 @@ public void GlobalSetup() m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); // for validating benchmark tests - switch (TestStringIndex) + switch (StringVariantIndex ) { case 1: m_testString = "Ascii characters 12345"; break; case 2: m_testString = "\" \n \r \t \b \f \\"; break; From fe150a2b752779ad6046545af26962f0956ffa66 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Fri, 16 Feb 2024 14:45:38 +0100 Subject: [PATCH 04/19] clean up --- .../Types/Encoders/JsonEncoderBenchmarks.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 5a6ba30ae8..e813518e0a 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -338,7 +338,6 @@ public void GlobalCleanup() #endregion } - [TestFixture, Category("JsonEncoder")] [SetCulture("en-us"), SetUICulture("en-us")] [NonParallelizable] @@ -350,7 +349,6 @@ public class JsonEncoderEscapeStringBenchmark [Params(1,2,3,4)] public int StringVariantIndex { get; set; } = 4; - [Test] [Benchmark] public void EscapeStringBenchmark1() { @@ -375,9 +373,8 @@ public void EscapeStringBenchmark2() public void OneTimeSetUp() { m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); - m_memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager);// new MemoryStream(); + m_memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager); m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); - m_testString = "Test string ascii, special characters \n \b and control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; } From cf599023e79828d43c61657ea40f2541e6c15a98 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Fri, 16 Feb 2024 15:44:26 +0100 Subject: [PATCH 05/19] more updates --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 8 ++-- .../Types/Encoders/JsonEncoderBenchmarks.cs | 45 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index a570b876b3..259abd7b38 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -35,6 +35,7 @@ public class JsonEncoder : IJsonEncoder private static readonly char s_rightCurlyBrace = '}'; private static readonly char s_leftSquareBracket = '['; private static readonly char s_rightSquareBracket = ']'; + private static readonly StringBuilder m_stringBuilder = new StringBuilder(); private Stream m_stream; private MemoryStream m_memoryStream; private StreamWriter m_writer; @@ -516,14 +517,13 @@ public void PopNamespace() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - StringBuilder m_stringBuilder = new StringBuilder(value.Length * 2); - - Dictionary substitution = new Dictionary(m_substitution); + m_stringBuilder.Clear(); + m_stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary - if (substitution.TryGetValue(ch, out string escapeSequence)) + if (m_substitution.TryGetValue(ch, out string escapeSequence)) { m_stringBuilder.Append(escapeSequence); } diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index e813518e0a..38de59c2bc 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -32,6 +32,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Xml; using BenchmarkDotNet.Attributes; @@ -346,8 +347,8 @@ public void GlobalCleanup() public class JsonEncoderEscapeStringBenchmark { - [Params(1,2,3,4)] - public int StringVariantIndex { get; set; } = 4; + [Params(1,2,3)] + public int StringVariantIndex { get; set; } = 3; [Benchmark] public void EscapeStringBenchmark1() @@ -355,7 +356,6 @@ public void EscapeStringBenchmark1() m_memoryStream.SetLength(0); m_memoryStream.Position = 0; EscapedStringToStream(m_testString); - m_streamWriter?.Flush(); } [Test] @@ -365,7 +365,6 @@ public void EscapeStringBenchmark2() m_memoryStream.SetLength(0); m_memoryStream.Position = 0; EscapeString(m_testString); - m_streamWriter?.Flush(); } #region Test Setup @@ -381,6 +380,7 @@ public void OneTimeSetUp() [OneTimeTearDown] public void OneTimeTearDown() { + m_streamWriter?.Flush(); var result = Encoding.UTF8.GetString(m_memoryStream.ToArray()); Assert.NotNull(result); @@ -407,9 +407,8 @@ public void GlobalSetup() // for validating benchmark tests switch (StringVariantIndex ) { - case 1: m_testString = "Ascii characters 12345"; break; - case 2: m_testString = "\" \n \r \t \b \f \\"; break; - case 3: m_testString = "\0 \x01 \x02 \x03 \x04"; break; + case 1: m_testString = "\" \n \r \t \b \f \\"; break; + case 2: m_testString = "\0 \x01 \x02 \x03 \x04"; break; default: m_testString = "Ascii characters , special characters \n \b & control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; break; } } @@ -417,6 +416,7 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { + m_streamWriter?.Flush(); m_streamWriter?.Dispose(); m_streamWriter = null; m_memoryStream?.Dispose(); @@ -459,26 +459,16 @@ private void EscapedStringToStream(string value) } } - private static readonly Dictionary m_replace = new Dictionary - { - { '\"', "\\\"" }, - { '\\', "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - { '\b', "\\b" }, - { '\f', "\\f" } - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - StringBuilder m_stringBuilder = new StringBuilder(value.Length * 2); - - Dictionary substitution = new Dictionary(m_replace); + m_stringBuilder.Clear(); + m_stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary - if (substitution.TryGetValue(ch, out string escapeSequence)) + if (m_replace.TryGetValue(ch, out string escapeSequence)) { m_stringBuilder.Append(escapeSequence); } @@ -497,14 +487,25 @@ private void EscapeString(string value) #endregion #region Private Fields + private static readonly StringBuilder m_stringBuilder = new StringBuilder(); private static string m_testString; private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; private MemoryStream m_memoryStream; private StreamWriter m_streamWriter; - private int m_streamSize = 2048; + private int m_streamSize = 1024; private static readonly char[] m_specialChars = new char[] { '\"', '\\', '\n', '\r', '\t', '\b', '\f', }; private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + private static readonly Dictionary m_replace = new Dictionary + { + { '\"', "\\\"" }, + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '\b', "\\b" }, + { '\f', "\\f" } + }; #endregion } } From 017bb129944184f72321d2db261222329c1ce4ba Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Fri, 16 Feb 2024 16:50:52 +0100 Subject: [PATCH 06/19] fixed concurency issue --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 18 ++++++++++-------- .../Types/Encoders/JsonEncoderBenchmarks.cs | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 259abd7b38..1c2d11cb90 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -16,6 +16,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.IO; using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Xml; namespace Opc.Ua @@ -35,7 +36,7 @@ public class JsonEncoder : IJsonEncoder private static readonly char s_rightCurlyBrace = '}'; private static readonly char s_leftSquareBracket = '['; private static readonly char s_rightSquareBracket = ']'; - private static readonly StringBuilder m_stringBuilder = new StringBuilder(); + private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); private Stream m_stream; private MemoryStream m_memoryStream; private StreamWriter m_writer; @@ -517,28 +518,29 @@ public void PopNamespace() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - m_stringBuilder.Clear(); - m_stringBuilder.EnsureCapacity(value.Length * 2); + StringBuilder stringBuilder = m_stringBuilderPool.Value; + stringBuilder.Clear(); + stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary if (m_substitution.TryGetValue(ch, out string escapeSequence)) { - m_stringBuilder.Append(escapeSequence); + stringBuilder.Append(escapeSequence); } else if (ch < 32) { - m_stringBuilder.Append("\\u"); - m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + stringBuilder.Append("\\u"); + stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } else { - m_stringBuilder.Append(ch); + stringBuilder.Append(ch); } } - m_writer.Write(m_stringBuilder); + m_writer.Write(stringBuilder); } private void WriteSimpleField(string fieldName, string value, bool quotes) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 38de59c2bc..ff6316dfc2 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -34,6 +34,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Xml; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; @@ -462,32 +463,33 @@ private void EscapedStringToStream(string value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - m_stringBuilder.Clear(); - m_stringBuilder.EnsureCapacity(value.Length * 2); + StringBuilder stringBuilder = m_stringBuilderPool.Value; + stringBuilder.Clear(); + stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary if (m_replace.TryGetValue(ch, out string escapeSequence)) { - m_stringBuilder.Append(escapeSequence); + stringBuilder.Append(escapeSequence); } else if (ch < 32) { - m_stringBuilder.Append("\\u"); - m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + stringBuilder.Append("\\u"); + stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } else { - m_stringBuilder.Append(ch); + stringBuilder.Append(ch); } } - m_streamWriter.Write(m_stringBuilder); + m_streamWriter.Write(stringBuilder); } #endregion #region Private Fields - private static readonly StringBuilder m_stringBuilder = new StringBuilder(); + private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); private static string m_testString; private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; From f648ea3112bb3aee8bf9330b8240756d54be69e0 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Sat, 17 Feb 2024 13:50:43 +0100 Subject: [PATCH 07/19] reverting Thread local code for stringbuilder --- Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 17 ++++++++--------- .../Types/Encoders/JsonEncoderBenchmarks.cs | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 1c2d11cb90..0c6f44564d 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -36,7 +36,7 @@ public class JsonEncoder : IJsonEncoder private static readonly char s_rightCurlyBrace = '}'; private static readonly char s_leftSquareBracket = '['; private static readonly char s_rightSquareBracket = ']'; - private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); + private StringBuilder m_stringBuilder = new StringBuilder(); private Stream m_stream; private MemoryStream m_memoryStream; private StreamWriter m_writer; @@ -518,29 +518,28 @@ public void PopNamespace() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - StringBuilder stringBuilder = m_stringBuilderPool.Value; - stringBuilder.Clear(); - stringBuilder.EnsureCapacity(value.Length * 2); + m_stringBuilder.Clear(); + m_stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary if (m_substitution.TryGetValue(ch, out string escapeSequence)) { - stringBuilder.Append(escapeSequence); + m_stringBuilder.Append(escapeSequence); } else if (ch < 32) { - stringBuilder.Append("\\u"); - stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + m_stringBuilder.Append("\\u"); + m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } else { - stringBuilder.Append(ch); + m_stringBuilder.Append(ch); } } - m_writer.Write(stringBuilder); + m_writer.Write(m_stringBuilder); } private void WriteSimpleField(string fieldName, string value, bool quotes) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index ff6316dfc2..d8e7e99040 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -463,33 +463,32 @@ private void EscapedStringToStream(string value) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { - StringBuilder stringBuilder = m_stringBuilderPool.Value; - stringBuilder.Clear(); - stringBuilder.EnsureCapacity(value.Length * 2); + m_stringBuilder.Clear(); + m_stringBuilder.EnsureCapacity(value.Length * 2); foreach (char ch in value) { // Check if ch is present in the dictionary if (m_replace.TryGetValue(ch, out string escapeSequence)) { - stringBuilder.Append(escapeSequence); + m_stringBuilder.Append(escapeSequence); } else if (ch < 32) { - stringBuilder.Append("\\u"); - stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + m_stringBuilder.Append("\\u"); + m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } else { - stringBuilder.Append(ch); + m_stringBuilder.Append(ch); } } - m_streamWriter.Write(stringBuilder); + m_streamWriter.Write(m_stringBuilder); } #endregion #region Private Fields - private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); + private StringBuilder m_stringBuilder = new StringBuilder(); private static string m_testString; private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; From ca17fb237a37c10d0c0194bff44f4a8eed09802a Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Sat, 17 Feb 2024 23:46:25 +0100 Subject: [PATCH 08/19] moved stream flush from tear down setup to the benchmark test --- .../Types/Encoders/JsonEncoderBenchmarks.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index d8e7e99040..e581257b7d 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -357,6 +357,7 @@ public void EscapeStringBenchmark1() m_memoryStream.SetLength(0); m_memoryStream.Position = 0; EscapedStringToStream(m_testString); + m_streamWriter.Flush(); } [Test] @@ -366,6 +367,7 @@ public void EscapeStringBenchmark2() m_memoryStream.SetLength(0); m_memoryStream.Position = 0; EscapeString(m_testString); + m_streamWriter.Flush(); } #region Test Setup @@ -374,14 +376,13 @@ public void OneTimeSetUp() { m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); m_memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager); - m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); + m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); m_testString = "Test string ascii, special characters \n \b and control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; } [OneTimeTearDown] public void OneTimeTearDown() { - m_streamWriter?.Flush(); var result = Encoding.UTF8.GetString(m_memoryStream.ToArray()); Assert.NotNull(result); @@ -403,7 +404,7 @@ public void OneTimeTearDown() public void GlobalSetup() { m_memoryStream = new MemoryStream(); - m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); + m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); // for validating benchmark tests switch (StringVariantIndex ) @@ -417,7 +418,6 @@ public void GlobalSetup() [GlobalCleanup] public void GlobalCleanup() { - m_streamWriter?.Flush(); m_streamWriter?.Dispose(); m_streamWriter = null; m_memoryStream?.Dispose(); From ef51849a2db22a882916082030c4a8f641eba599 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 19 Feb 2024 10:49:08 +0100 Subject: [PATCH 09/19] more variantions --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 12 +- .../Types/Encoders/JsonEncoderBenchmarks.cs | 702 ++++++++++++++++-- .../Types/Encoders/JsonEncoderTests.cs | 1 + 3 files changed, 665 insertions(+), 50 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 1c2d11cb90..d3a426e95d 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -36,6 +36,7 @@ public class JsonEncoder : IJsonEncoder private static readonly char s_rightCurlyBrace = '}'; private static readonly char s_leftSquareBracket = '['; private static readonly char s_rightSquareBracket = ']'; + private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(false); private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); private Stream m_stream; private MemoryStream m_memoryStream; @@ -89,12 +90,12 @@ public JsonEncoder( if (m_stream == null) { m_memoryStream = new MemoryStream(); - m_writer = new StreamWriter(m_memoryStream, new UTF8Encoding(false), streamSize, false); + m_writer = new StreamWriter(m_memoryStream, s_utf8Encoding, streamSize, false); m_leaveOpen = false; } else { - m_writer = new StreamWriter(m_stream, new UTF8Encoding(false), streamSize, m_leaveOpen); + m_writer = new StreamWriter(m_stream, s_utf8Encoding, streamSize, m_leaveOpen); } InitializeWriter(); @@ -119,7 +120,7 @@ public JsonEncoder( if (m_writer == null) { m_stream = new MemoryStream(); - m_writer = new StreamWriter(m_stream, new UTF8Encoding(false), kStreamWriterBufferSize); + m_writer = new StreamWriter(m_stream, s_utf8Encoding, kStreamWriterBufferSize); } InitializeWriter(); @@ -295,11 +296,11 @@ public int Close() { if (m_topLevelIsArray) { - m_writer.Write("]"); + m_writer.Write(']'); } else { - m_writer.Write("}"); + m_writer.Write('}'); } } @@ -515,7 +516,6 @@ public void PopNamespace() /// Escapes a string and writes it to the stream. /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EscapeString(string value) { StringBuilder stringBuilder = m_stringBuilderPool.Value; diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index ff6316dfc2..9cff39ec5f 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -28,21 +28,16 @@ * ======================================================================*/ using System; -using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Threading; -using System.Xml; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; -using FastSerialization; -using Microsoft.Extensions.Logging; using Microsoft.IO; using NUnit.Framework; -using NUnit.Framework.Constraints; using Opc.Ua.Bindings; namespace Opc.Ua.Core.Tests.Types.Encoders @@ -80,7 +75,7 @@ public void StreamWriter() [Test] public void StreamWriterRecyclableMemoryStream() { - using (var memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager)) + using (var memoryStream = new RecyclableMemoryStream(m_memoryManager)) using (var test = new StreamWriter(memoryStream, Encoding.UTF8, StreamSize)) test.Flush(); } @@ -200,8 +195,8 @@ public void OneTimeSetUp() // for validating benchmark tests m_context = new ServiceMessageContext(); m_memoryStream = new MemoryStream(); - m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); - m_recyclableMemoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager); + m_memoryManager = new RecyclableMemoryStreamManager(); + m_recyclableMemoryStream = new RecyclableMemoryStream(m_memoryManager); m_bufferManager = new BufferManager(nameof(BinaryEncoder), kBufferSize); m_arraySegmentStream = new ArraySegmentStream(m_bufferManager, kBufferSize, 0, kBufferSize); } @@ -231,8 +226,8 @@ public void GlobalSetup() // for validating benchmark tests m_context = new ServiceMessageContext(); m_memoryStream = new MemoryStream(); - m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); - m_recyclableMemoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager); + m_memoryManager = new RecyclableMemoryStreamManager(); + m_recyclableMemoryStream = new RecyclableMemoryStream(m_memoryManager); m_bufferManager = new BufferManager(nameof(BinaryEncoder), kBufferSize); m_arraySegmentStream = new ArraySegmentStream(m_bufferManager, kBufferSize, 0, kBufferSize); } @@ -260,8 +255,8 @@ public void GlobalCleanup() private static IList s_list = new List() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private IServiceMessageContext m_context; private MemoryStream m_memoryStream; - private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; - private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; + private RecyclableMemoryStreamManager m_memoryManager; + private RecyclableMemoryStream m_recyclableMemoryStream; private BufferManager m_bufferManager; private ArraySegmentStream m_arraySegmentStream; #endregion @@ -347,35 +342,249 @@ public void GlobalCleanup() [DisassemblyDiagnoser(printSource: true)] public class JsonEncoderEscapeStringBenchmark { + public const int InnerLoops = 100; + [DatapointSource] + [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] + public int StringVariantIndex { get; set; } = 1; - [Params(1,2,3)] - public int StringVariantIndex { get; set; } = 3; + [DatapointSource] + // for benchmarking with different escaped strings + public static readonly string[] EscapeTestStrings = + { + // The use case without escape characters, plain text + "The quick brown fox jumps over the lazy dog.", + // The use case with many control characters escaped, 1 char spaces + "\" \n \r \t \b \f \\ ", + // The use case with many control characters escaped, 2 char spaces + " \" \n \r \t \b \f \\ ", + // The use case with many control characters escaped, 3 char spaces + " \" \n \r \t \b \f \\ ", + // The use case with many control characters escaped, 5 char spaces + " \" \n \r \t \b \f \\ ", + // The use case with many binary characters escaped, 1 char spaces + "\0 \x01 \x02 \x03 \x04 ", + // The use case with many binary characters escaped, 2 char spaces + " \0 \x01 \x02 \x03 \x04 ", + // The use case with many binary characters escaped, 3 char spaces + " \0 \x01 \x02 \x03 \x04 ", + // The use case with many binary characters escaped, 5 char spaces + " \0 \x01 \x02 \x03 \x04 ", + // The use case with all escape characters and a long string + "Ascii characters, special characters \n \b & control characters \0 \x04 ␀ ␁ ␂ ␃ ␄. This is a test.", + }; + + /// + /// Benchmark encoding of the previous implementation. + /// + [Benchmark(Baseline = true)] + public void EscapeStringLegacy() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapedStringLegacy(m_testString); + } + m_streamWriter.Flush(); + } + /// + /// Benchmark encoding of the previous implementation with snall improvement for binary encoding. + /// [Benchmark] - public void EscapeStringBenchmark1() + public void EscapeStringLegacyPlus() { - m_memoryStream.SetLength(0); m_memoryStream.Position = 0; - EscapedStringToStream(m_testString); + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapedStringLegacyPlus(m_testString); + } + m_streamWriter.Flush(); } - [Test] + /// + /// A new implementation using StringBuilder. + /// + [Benchmark] + public void EscapeStringStringBuilder() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeString(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ThreadLocal StringBuilder. + /// + [Benchmark] + public void EscapeStringThreadLocal() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringThreadLocal(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ReadOnlySpan. + /// + [Benchmark] + public void EscapeStringSpan() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringSpan(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ReadOnlySpan and char write. + /// + [Benchmark] + public void EscapeStringSpanChars() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringSpanChars(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ReadOnlySpan and char write. + /// [Benchmark] - public void EscapeStringBenchmark2() + public void EscapeStringSpanCharsInline() { - m_memoryStream.SetLength(0); m_memoryStream.Position = 0; - EscapeString(m_testString); + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringSpanCharsInline(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ReadOnlySpan and IndexOf. + /// + [Benchmark] + public void EscapeStringSpanIndex() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringSpanIndex(m_testString); + } + m_streamWriter.Flush(); + } + + /// + /// A new implementation using ReadOnlySpan and Dictionary. + /// + [Benchmark] + public void EscapeStringSpanDict() + { + m_memoryStream.Position = 0; + int repeats = InnerLoops; + while (repeats-- > 0) + { + EscapeStringSpanDict(m_testString); + } + m_streamWriter.Flush(); + } + + [Theory] + [TestCase("No Escape chars", 0)] + [TestCase("control chars escaped", 1)] + [TestCase("binary chars escaped", 2)] + [TestCase("mixed escape chars", 3)] + public void EscapeStringValidation(string name, int index) + { + m_memoryStream = new RecyclableMemoryStream(m_memoryManager); + m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); + + m_testString = EscapeTestStrings[index]; + TestContext.Out.WriteLine(m_testString); + var testArray = m_testString.ToCharArray(); + + m_memoryStream.Position = 0; + EscapeStringLegacy(); + m_streamWriter.Flush(); + byte[] resultLegacy = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultLegacy)); + + m_memoryStream.Position = 0; + EscapeStringLegacyPlus(); + m_streamWriter.Flush(); + byte[] resultLegacyPlus = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultLegacyPlus)); + + m_memoryStream.Position = 0; + EscapeStringStringBuilder(); + m_streamWriter.Flush(); + byte[] result = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(result)); + + m_memoryStream.Position = 0; + EscapeStringSpan(m_testString); + m_streamWriter.Flush(); + byte[] resultSpan = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpan)); + + m_memoryStream.Position = 0; + EscapeStringSpanChars(m_testString); + m_streamWriter.Flush(); + byte[] resultSpanChars = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanChars)); + + m_memoryStream.Position = 0; + EscapeStringSpanCharsInline(m_testString); + m_streamWriter.Flush(); + byte[] resultSpanCharsInline = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanCharsInline)); + + m_memoryStream.Position = 0; + EscapeStringSpanIndex(m_testString); + m_streamWriter.Flush(); + byte[] resultSpanIndex = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanIndex)); + + m_memoryStream.Position = 0; + EscapeStringSpanDict(m_testString); + m_streamWriter.Flush(); + byte[] resultSpanDict = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultSpanDict)); + + Assert.IsTrue(Utils.IsEqual(resultLegacy, result)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultLegacyPlus)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpan)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanChars)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanIndex)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanDict)); } #region Test Setup [OneTimeSetUp] public void OneTimeSetUp() { - m_memoryManager = new Microsoft.IO.RecyclableMemoryStreamManager(); - m_memoryStream = new Microsoft.IO.RecyclableMemoryStream(m_memoryManager); - m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); - m_testString = "Test string ascii, special characters \n \b and control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; + m_memoryManager = new RecyclableMemoryStreamManager(); + m_memoryStream = new RecyclableMemoryStream(m_memoryManager); + m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); } [OneTimeTearDown] @@ -389,29 +598,21 @@ public void OneTimeTearDown() m_streamWriter = null; m_memoryStream?.Dispose(); m_memoryStream = null; - m_recyclableMemoryStream?.Dispose(); - m_recyclableMemoryStream = null; m_memoryManager = null; } #endregion #region Benchmark Setup - /// + /// 4 /// Set up some variables for benchmarks. /// [GlobalSetup] public void GlobalSetup() { - m_memoryStream = new MemoryStream(); - m_streamWriter = new StreamWriter(m_memoryStream, Encoding.UTF8, m_streamSize, false); - - // for validating benchmark tests - switch (StringVariantIndex ) - { - case 1: m_testString = "\" \n \r \t \b \f \\"; break; - case 2: m_testString = "\0 \x01 \x02 \x03 \x04"; break; - default: m_testString = "Ascii characters , special characters \n \b & control characters \0 \x04 ␀ ␁ ␂ ␃ ␄"; break; - } + m_memoryManager = new RecyclableMemoryStreamManager(); + m_memoryStream = new RecyclableMemoryStream(m_memoryManager); + m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); + m_testString = EscapeTestStrings[StringVariantIndex - 1]; } [GlobalCleanup] @@ -422,14 +623,15 @@ public void GlobalCleanup() m_streamWriter = null; m_memoryStream?.Dispose(); m_memoryStream = null; - m_recyclableMemoryStream?.Dispose(); - m_recyclableMemoryStream = null; m_memoryManager = null; } #endregion #region Private Methods - private void EscapedStringToStream(string value) + /// + /// Version used previously in JsonEncoder. + /// + private void EscapedStringLegacy(string value) { foreach (char ch in value) { @@ -460,8 +662,419 @@ private void EscapedStringToStream(string value) } } + /// + /// Version used previously in JsonEncoder plus improvement of binary encoding. + /// + /// + /// For the underlying stream writer it is faster to write two chars than a 2 char string. + /// + private void EscapedStringLegacyPlus(string value) + { + foreach (char ch in value) + { + bool found = false; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + m_streamWriter.Write('\\'); + m_streamWriter.Write(m_substitution[ii]); + found = true; + break; + } + } + + if (!found) + { + if (ch < 32) + { + m_streamWriter.Write('\\'); + m_streamWriter.Write('u'); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + continue; + } + + m_streamWriter.Write(ch); + } + } + } + + /// + /// Using a span to escape the string, write strings to stream writer if possible. + /// + private void EscapeStringSpan(string value) + { + ReadOnlySpan charSpan = value.AsSpan(); + int lastOffset = 0; + + for (int i = 0; i < charSpan.Length; i++) + { + bool found = false; + char ch = charSpan[i]; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write(m_substitutionStrings[ii]); + found = true; + break; + } + } + + // Check if ch is present in the dictionary + if (!found && ch < 32) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write("\\u"); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + } + if (lastOffset == 0) + { + m_streamWriter.Write(value); + } + else if (lastOffset < charSpan.Length) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#endif + } + } + + /// + /// Use span to escape the string, write only chars to stream writer. + /// + /// + private void EscapeStringSpanChars(string value) + { + ReadOnlySpan charSpan = value.AsSpan(); + + int lastOffset = 0; + for (int i = 0; i < charSpan.Length; i++) + { + bool found = false; + char ch = charSpan[i]; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write('\\'); + m_streamWriter.Write(m_substitution[ii]); + found = true; + break; + } + } + + // Check if ch is present in the dictionary + if (!found && ch < 32) + { + if (lastOffset < i - 1) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + else + { + while (lastOffset < i) + { + m_streamWriter.Write(charSpan[lastOffset++]); + } + } + lastOffset = i + 1; + m_streamWriter.Write('\\'); + m_streamWriter.Write('u'); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + } + + if (lastOffset == 0) + { +#if NET48 + m_streamWriter.Write(value); +#else + m_streamWriter.Write(charSpan); +#endif + } + else if (lastOffset < charSpan.Length) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#endif + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int index) + { + if (lastOffset < index - 2) + { +#if NET48 + m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); +#else + m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset)); +#endif + } + else + { + while (lastOffset < index) + { + m_streamWriter.Write(valueSpan[lastOffset++]); + } + } + lastOffset = index + 1; + } + + /// + /// Write only chars to stream writer, inline the write sequence for readability. + /// + /// + private void EscapeStringSpanCharsInline(string value) + { + ReadOnlySpan charSpan = value.AsSpan(); + int lastOffset = 0; + + for (int i = 0; i < charSpan.Length; i++) + { + bool found = false; + char ch = charSpan[i]; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + WriteSpan(ref lastOffset, charSpan, i); + m_streamWriter.Write('\\'); + m_streamWriter.Write(m_substitution[ii]); + found = true; + break; + } + } + + // Check if ch is present in the dictionary + if (!found && ch < 32) + { + WriteSpan(ref lastOffset, charSpan, i); + m_streamWriter.Write('\\'); + m_streamWriter.Write('u'); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + } + + if (lastOffset == 0) + { +#if NET48 + m_streamWriter.Write(value); +#else + m_streamWriter.Write(charSpan); +#endif + } + else + { + WriteSpan(ref lastOffset, charSpan, charSpan.Length); + } + } + + private void EscapeStringSpanIndex(string value) + { + ReadOnlySpan charSpan = value.AsSpan(); + + int lastOffset = 0; + for (int i = 0; i < charSpan.Length; i++) + { + char ch = charSpan[i]; + + int index = m_specialString.IndexOf(ch); + if (index >= 0) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write(m_substitutionStrings[index]); + continue; + } + + // Check if ch is present in the dictionary + if (ch < 32) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write('\\'); + m_streamWriter.Write('u'); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + } + if (lastOffset == 0) + { + m_streamWriter.Write(value); + } + else if (lastOffset < charSpan.Length) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#endif + } + } + +#if mist + public static byte[] EscapeValue( + ReadOnlySpan utf8Value, + int firstEscapeIndexVal, + JavaScriptEncoder encoder) + { + Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length); + Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length); + + byte[]? valueArray = null; + + int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); + + Span escapedValue = length <= JsonConstants.StackallocByteThreshold ? + stackalloc byte[JsonConstants.StackallocByteThreshold] : + (valueArray = ArrayPool.Shared.Rent(length)); + + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written); + + byte[] escapedString = escapedValue.Slice(0, written).ToArray(); + + if (valueArray != null) + { + ArrayPool.Shared.Return(valueArray); + } + + return escapedString; + } +#endif + private void EscapeStringSpanDict(string value) + { + ReadOnlySpan charSpan = value.AsSpan(); + + int lastOffset = 0; + for (int i = 0; i < charSpan.Length; i++) + { + char ch = charSpan[i]; + + if (m_replace.TryGetValue(ch, out string escapeSequence)) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write(escapeSequence); + continue; + } + + // Check if ch is present in the dictionary + if (ch < 32) + { + if (lastOffset < i) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#endif + } + lastOffset = i + 1; + m_streamWriter.Write('\\'); + m_streamWriter.Write('u'); + m_streamWriter.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + } + if (lastOffset == 0) + { + m_streamWriter.Write(value); + } + else if (lastOffset < charSpan.Length) + { +#if NET48 + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#endif + } + } + private void EscapeString(string value) + { + StringBuilder stringBuilder = new StringBuilder(value.Length * 2); + + foreach (char ch in value) + { + // Check if ch is present in the dictionary + if (m_replace.TryGetValue(ch, out string escapeSequence)) + { + stringBuilder.Append(escapeSequence); + } + else if (ch < 32) + { + stringBuilder.Append("\\u"); + stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + } + else + { + stringBuilder.Append(ch); + } + } + m_streamWriter.Write(stringBuilder); + } + + private void EscapeStringThreadLocal(string value) { StringBuilder stringBuilder = m_stringBuilderPool.Value; stringBuilder.Clear(); @@ -491,13 +1104,14 @@ private void EscapeString(string value) #region Private Fields private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); private static string m_testString; - private Microsoft.IO.RecyclableMemoryStreamManager m_memoryManager; - private Microsoft.IO.RecyclableMemoryStream m_recyclableMemoryStream; - private MemoryStream m_memoryStream; + private RecyclableMemoryStreamManager m_memoryManager; + private RecyclableMemoryStream m_memoryStream; private StreamWriter m_streamWriter; private int m_streamSize = 1024; + private static readonly string m_specialString = "\"\\\n\r\t\b\f"; private static readonly char[] m_specialChars = new char[] { '\"', '\\', '\n', '\r', '\t', '\b', '\f', }; private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + private static readonly string[] m_substitutionStrings = new string[] { "\\\"", "\\\\", "\\n", "\\r", "\\t", "\\b", "\\f" }; private static readonly Dictionary m_replace = new Dictionary { { '\"', "\\\"" }, diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs index e04feba434..cc88123717 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs @@ -949,6 +949,7 @@ public void DateTimeEncodeStringTest(DateTime testDateTime) TestContext.Out.WriteLine("Decoded: {0} {1}", decodedO.ToString("o"), decodedString.ToString("o")); Assert.AreEqual(decodedO, decodedString); + Assert.AreEqual(testDateTime, decodedO); } #endregion From 16705cc1c05ff3d8f5f3bcfc93f01bca49891a12 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Mon, 19 Feb 2024 14:00:22 +0100 Subject: [PATCH 10/19] added resultSpanCharsInline and conditonal for NETCOREAPP2_1_OR_GREATER --- .../Types/Encoders/JsonEncoderBenchmarks.cs | 121 +++++++----------- 1 file changed, 47 insertions(+), 74 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 48a06920b1..12a64d1331 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -574,6 +574,7 @@ public void EscapeStringValidation(string name, int index) Assert.IsTrue(Utils.IsEqual(resultLegacy, resultLegacyPlus)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpan)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanChars)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanCharsInline)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanIndex)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanDict)); } @@ -717,10 +718,10 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -735,10 +736,10 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -752,10 +753,10 @@ private void EscapeStringSpan(string value) } else if (lastOffset < charSpan.Length) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); #endif } } @@ -780,10 +781,10 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -799,10 +800,10 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i - 1) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } else @@ -821,18 +822,18 @@ private void EscapeStringSpanChars(string value) if (lastOffset == 0) { -#if NET48 - m_streamWriter.Write(value); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan); +#else + m_streamWriter.Write(value); #endif } else if (lastOffset < charSpan.Length) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); #endif } } @@ -842,10 +843,10 @@ private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int ind { if (lastOffset < index - 2) { -#if NET48 - m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset)); +#else + m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); #endif } else @@ -896,10 +897,10 @@ private void EscapeStringSpanCharsInline(string value) if (lastOffset == 0) { -#if NET48 - m_streamWriter.Write(value); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan); +#else + m_streamWriter.Write(value); #endif } else @@ -922,10 +923,10 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -938,10 +939,10 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -956,43 +957,14 @@ private void EscapeStringSpanIndex(string value) } else if (lastOffset < charSpan.Length) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); #endif } } -#if mist - public static byte[] EscapeValue( - ReadOnlySpan utf8Value, - int firstEscapeIndexVal, - JavaScriptEncoder encoder) - { - Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length); - Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length); - - byte[]? valueArray = null; - - int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - - Span escapedValue = length <= JsonConstants.StackallocByteThreshold ? - stackalloc byte[JsonConstants.StackallocByteThreshold] : - (valueArray = ArrayPool.Shared.Rent(length)); - - JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written); - - byte[] escapedString = escapedValue.Slice(0, written).ToArray(); - - if (valueArray != null) - { - ArrayPool.Shared.Return(valueArray); - } - - return escapedString; - } -#endif private void EscapeStringSpanDict(string value) { ReadOnlySpan charSpan = value.AsSpan(); @@ -1006,10 +978,10 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -1022,10 +994,10 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); #endif } lastOffset = i + 1; @@ -1040,10 +1012,10 @@ private void EscapeStringSpanDict(string value) } else if (lastOffset < charSpan.Length) { -#if NET48 - m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); -#else +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); +#else + m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); #endif } } @@ -1100,6 +1072,7 @@ private void EscapeStringThreadLocal(string value) #endregion #region Private Fields + private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); private StringBuilder m_stringBuilder = new StringBuilder(); private static string m_testString; private RecyclableMemoryStreamManager m_memoryManager; From 1e73002f13fb71dca4ef64340eca856f19ba35df Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 13:23:49 +0100 Subject: [PATCH 11/19] updated testcase and fixed Threadlocal case --- .../Types/Encoders/JsonEncoderBenchmarks.cs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 12a64d1331..7ed4fa6d3f 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -340,7 +340,7 @@ public void GlobalCleanup() [NonParallelizable] [MemoryDiagnoser] [DisassemblyDiagnoser(printSource: true)] - public class JsonEncoderEscapeStringBenchmark + public class JsonEncoderEscapeStringBenchmarks { public const int InnerLoops = 100; [DatapointSource] @@ -510,14 +510,17 @@ public void EscapeStringSpanDict() [Theory] [TestCase("No Escape chars", 0)] - [TestCase("control chars escaped", 1)] - [TestCase("binary chars escaped", 2)] - [TestCase("mixed escape chars", 3)] + [TestCase("control chars escaped, 1 char space", 1)] + [TestCase("control chars escaped, 2 char spaces", 2)] + [TestCase("control chars escaped, 3 char spaces", 3)] + [TestCase("control chars escaped, 5 char spaces", 4)] + [TestCase("binary chars escaped, 1 char space", 5)] + [TestCase("binary chars escaped, 2 char spaces", 6)] + [TestCase("binary chars escaped, 3 char spaces", 7)] + [TestCase("binary chars escaped, 5 char spaces", 8)] + [TestCase("all escape chars and long string", 9)] public void EscapeStringValidation(string name, int index) { - m_memoryStream = new RecyclableMemoryStream(m_memoryManager); - m_streamWriter = new StreamWriter(m_memoryStream, new UTF8Encoding(false), m_streamSize, false); - m_testString = EscapeTestStrings[index]; TestContext.Out.WriteLine(m_testString); var testArray = m_testString.ToCharArray(); @@ -540,6 +543,12 @@ public void EscapeStringValidation(string name, int index) byte[] result = m_memoryStream.ToArray(); TestContext.Out.WriteLine(Encoding.UTF8.GetString(result)); + m_memoryStream.Position = 0; + EscapeStringThreadLocal(); + m_streamWriter.Flush(); + byte[] resultThreadLocal = m_memoryStream.ToArray(); + TestContext.Out.WriteLine(Encoding.UTF8.GetString(resultThreadLocal)); + m_memoryStream.Position = 0; EscapeStringSpan(m_testString); m_streamWriter.Flush(); @@ -572,6 +581,7 @@ public void EscapeStringValidation(string name, int index) Assert.IsTrue(Utils.IsEqual(resultLegacy, result)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultLegacyPlus)); + Assert.IsTrue(Utils.IsEqual(resultLegacy, resultThreadLocal)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpan)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanChars)); Assert.IsTrue(Utils.IsEqual(resultLegacy, resultSpanCharsInline)); @@ -591,9 +601,6 @@ public void OneTimeSetUp() [OneTimeTearDown] public void OneTimeTearDown() { - var result = Encoding.UTF8.GetString(m_memoryStream.ToArray()); - Assert.NotNull(result); - m_streamWriter?.Dispose(); m_streamWriter = null; m_memoryStream?.Dispose(); @@ -1055,25 +1062,24 @@ private void EscapeStringThreadLocal(string value) // Check if ch is present in the dictionary if (m_replace.TryGetValue(ch, out string escapeSequence)) { - m_stringBuilder.Append(escapeSequence); + stringBuilder.Append(escapeSequence); } else if (ch < 32) { - m_stringBuilder.Append("\\u"); - m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + stringBuilder.Append("\\u"); + stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } else { - m_stringBuilder.Append(ch); + stringBuilder.Append(ch); } } - m_streamWriter.Write(m_stringBuilder); + m_streamWriter.Write(stringBuilder); } #endregion #region Private Fields private ThreadLocal m_stringBuilderPool = new ThreadLocal(() => new StringBuilder()); - private StringBuilder m_stringBuilder = new StringBuilder(); private static string m_testString; private RecyclableMemoryStreamManager m_memoryManager; private RecyclableMemoryStream m_memoryStream; From 0631d86d7ed40f60495e1c0721f5aadadcb5caf6 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 14:02:06 +0100 Subject: [PATCH 12/19] EscapeString updated evaluating the benchmark results --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 100 +++++++++++++----- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index f5d42bdb49..aea784c58e 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -501,46 +501,98 @@ public void PopNamespace() m_namespaces.Pop(); } - private static readonly Dictionary m_substitution = new Dictionary - { - { s_quotation, "\\\"" }, - { s_backslash, "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - { '\b', "\\b" }, - { '\f', "\\f" } - }; + private static readonly char[] m_specialChars = new char[] { s_quotation, s_backslash, '\n', '\r', '\t', '\b', '\f', }; +#if NETCOREAPP2_1_OR_GREATER + private static readonly string[] m_substitutionStrings = new string[] { "\\\"", "\\\\", "\\n", "\\r", "\\t", "\\b", "\\f" }; /// - /// Escapes a string and writes it to the stream. + /// Using a span to escape the string, write strings to stream writer if possible. /// /// private void EscapeString(string value) { - m_stringBuilder.Clear(); - m_stringBuilder.EnsureCapacity(value.Length * 2); + ReadOnlySpan charSpan = value.AsSpan(); + int lastOffset = 0; - foreach (char ch in value) + for (int i = 0; i < charSpan.Length; i++) { + bool found = false; + char ch = charSpan[i]; + + for (int ii = 0; ii < m_specialChars.Length; ii++) + { + if (m_specialChars[ii] == ch) + { + if (lastOffset < i) + { + m_writer.Write(charSpan.Slice(lastOffset, i - lastOffset)); + } + lastOffset = i + 1; + m_writer.Write(m_substitutionStrings[ii]); + found = true; + break; + } + } + // Check if ch is present in the dictionary - if (m_substitution.TryGetValue(ch, out string escapeSequence)) + if (!found && ch < 32) { - m_stringBuilder.Append(escapeSequence); + if (lastOffset < i) + { + m_writer.Write(charSpan.Slice(lastOffset, i - lastOffset)); + } + lastOffset = i + 1; + m_writer.Write("\\u"); + m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } - else if (ch < 32) + } + if (lastOffset == 0) + { + m_writer.Write(value); + } + else if (lastOffset < charSpan.Length) + { + m_writer.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); + } + } +#else + private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + + /// + /// Escapes a string and writes it to the stream. + /// + /// + private void EscapeString(string value) + { + foreach (char ch in value) + { + bool found = false; + + for (int ii = 0; ii < m_specialChars.Length; ii++) { - m_stringBuilder.Append("\\u"); - m_stringBuilder.Append(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + if (m_specialChars[ii] == ch) + { + m_writer.Write('\\'); + m_writer.Write(m_substitution[ii]); + found = true; + break; + } } - else + + if (!found) { - m_stringBuilder.Append(ch); + if (ch < 32) + { + m_writer.Write('\\'); + m_writer.Write('u'); + m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); + continue; + } + m_writer.Write(ch); } } - - m_writer.Write(m_stringBuilder); } +#endif private void WriteSimpleField(string fieldName, string value, bool quotes) { @@ -2221,7 +2273,7 @@ public void WriteEnumeratedArray(string fieldName, Array values, System.Type sys PopArray(); } - #endregion +#endregion #region Public Methods /// From efb3867597865904380c5232c2f63769ef097715 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 14:08:11 +0100 Subject: [PATCH 13/19] cleanup --- Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 1 - .../Types/Encoders/JsonEncoderBenchmarks.cs | 7 ------- 2 files changed, 8 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index aea784c58e..6465382bd8 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -534,7 +534,6 @@ private void EscapeString(string value) } } - // Check if ch is present in the dictionary if (!found && ch < 32) { if (lastOffset < i) diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 7ed4fa6d3f..fa8f0926a2 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -738,7 +738,6 @@ private void EscapeStringSpan(string value) } } - // Check if ch is present in the dictionary if (!found && ch < 32) { if (lastOffset < i) @@ -802,7 +801,6 @@ private void EscapeStringSpanChars(string value) } } - // Check if ch is present in the dictionary if (!found && ch < 32) { if (lastOffset < i - 1) @@ -892,7 +890,6 @@ private void EscapeStringSpanCharsInline(string value) } } - // Check if ch is present in the dictionary if (!found && ch < 32) { WriteSpan(ref lastOffset, charSpan, i); @@ -941,7 +938,6 @@ private void EscapeStringSpanIndex(string value) continue; } - // Check if ch is present in the dictionary if (ch < 32) { if (lastOffset < i) @@ -996,7 +992,6 @@ private void EscapeStringSpanDict(string value) continue; } - // Check if ch is present in the dictionary if (ch < 32) { if (lastOffset < i) @@ -1033,7 +1028,6 @@ private void EscapeString(string value) foreach (char ch in value) { - // Check if ch is present in the dictionary if (m_replace.TryGetValue(ch, out string escapeSequence)) { stringBuilder.Append(escapeSequence); @@ -1059,7 +1053,6 @@ private void EscapeStringThreadLocal(string value) foreach (char ch in value) { - // Check if ch is present in the dictionary if (m_replace.TryGetValue(ch, out string escapeSequence)) { stringBuilder.Append(escapeSequence); From 9e420c663e531effe258aea71e29d8d22dae8e8c Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 14:19:52 +0100 Subject: [PATCH 14/19] more cleanup --- Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 6465382bd8..6bd275f496 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -16,7 +16,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.IO; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using System.Xml; namespace Opc.Ua @@ -296,11 +295,11 @@ public int Close() { if (m_topLevelIsArray) { - m_writer.Write(']'); + m_writer.Write(s_rightSquareBracket); } else { - m_writer.Write('}'); + m_writer.Write(s_rightCurlyBrace); } } @@ -571,7 +570,7 @@ private void EscapeString(string value) { if (m_specialChars[ii] == ch) { - m_writer.Write('\\'); + m_writer.Write(s_backslash); m_writer.Write(m_substitution[ii]); found = true; break; @@ -582,7 +581,7 @@ private void EscapeString(string value) { if (ch < 32) { - m_writer.Write('\\'); + m_writer.Write(s_backslash); m_writer.Write('u'); m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); continue; From c37a954add8af2e27f6ea5f44396192f16d68caf Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 14:22:20 +0100 Subject: [PATCH 15/19] fixed formating --- Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 6bd275f496..40a3d61bc6 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -2271,7 +2271,7 @@ public void WriteEnumeratedArray(string fieldName, Array values, System.Type sys PopArray(); } -#endregion + #endregion #region Public Methods /// From ec6e37357793d34325ed19a4d97316c983113958 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 18:32:43 +0100 Subject: [PATCH 16/19] updated to use escape string inline version instead of index version and changed .net conditionals supporting Span --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 46 +++++++++++-------- .../Types/Encoders/JsonEncoderBenchmarks.cs | 30 ++++++------ 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 40a3d61bc6..86d5244fbf 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -501,9 +501,9 @@ public void PopNamespace() } private static readonly char[] m_specialChars = new char[] { s_quotation, s_backslash, '\n', '\r', '\t', '\b', '\f', }; -#if NETCOREAPP2_1_OR_GREATER - private static readonly string[] m_substitutionStrings = new string[] { "\\\"", "\\\\", "\\n", "\\r", "\\t", "\\b", "\\f" }; + private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER /// /// Using a span to escape the string, write strings to stream writer if possible. /// @@ -522,12 +522,9 @@ private void EscapeString(string value) { if (m_specialChars[ii] == ch) { - if (lastOffset < i) - { - m_writer.Write(charSpan.Slice(lastOffset, i - lastOffset)); - } - lastOffset = i + 1; - m_writer.Write(m_substitutionStrings[ii]); + WriteSpan(ref lastOffset, charSpan, i); + m_writer.Write('\\'); + m_writer.Write(m_substitution[ii]); found = true; break; } @@ -535,27 +532,40 @@ private void EscapeString(string value) if (!found && ch < 32) { - if (lastOffset < i) - { - m_writer.Write(charSpan.Slice(lastOffset, i - lastOffset)); - } - lastOffset = i + 1; - m_writer.Write("\\u"); + WriteSpan(ref lastOffset, charSpan, i); + m_writer.Write('\\'); + m_writer.Write('u'); m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture)); } } + if (lastOffset == 0) { m_writer.Write(value); } - else if (lastOffset < charSpan.Length) + else { - m_writer.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); + WriteSpan(ref lastOffset, charSpan, charSpan.Length); } } -#else - private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int index) + { + if (lastOffset < index - 2) + { + m_writer.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); + } + else + { + while (lastOffset < index) + { + m_writer.Write(valueSpan[lastOffset++]); + } + } + lastOffset = index + 1; + } +#else /// /// Escapes a string and writes it to the stream. /// diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index fa8f0926a2..25ab1aaef0 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -725,7 +725,7 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -742,7 +742,7 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -759,7 +759,7 @@ private void EscapeStringSpan(string value) } else if (lastOffset < charSpan.Length) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -787,7 +787,7 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -805,7 +805,7 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i - 1) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -827,7 +827,7 @@ private void EscapeStringSpanChars(string value) if (lastOffset == 0) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan); #else m_streamWriter.Write(value); @@ -835,7 +835,7 @@ private void EscapeStringSpanChars(string value) } else if (lastOffset < charSpan.Length) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -848,7 +848,7 @@ private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int ind { if (lastOffset < index - 2) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset)); #else m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); @@ -901,7 +901,7 @@ private void EscapeStringSpanCharsInline(string value) if (lastOffset == 0) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan); #else m_streamWriter.Write(value); @@ -927,7 +927,7 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -942,7 +942,7 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -960,7 +960,7 @@ private void EscapeStringSpanIndex(string value) } else if (lastOffset < charSpan.Length) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -981,7 +981,7 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -996,7 +996,7 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -1014,7 +1014,7 @@ private void EscapeStringSpanDict(string value) } else if (lastOffset < charSpan.Length) { -#if NETCOREAPP2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); From 25e3df352e94aa4ac8162ef5f9250a4152d5084c Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 18:48:27 +0100 Subject: [PATCH 17/19] Fixed warnings (CA1305) related to ToString() method --- .../TypeSystemClientTest.cs | 3 +- .../Types/JsonEncoderTests.cs | 5 ++- .../Types/MockResolverTests.cs | 3 +- .../Types/Encoders/JsonEncoderTests.cs | 38 +++++++++---------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs index 1b8bbab5c1..c6d5ffc64a 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -116,7 +117,7 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null) await m_clientFixture.LoadClientConfiguration(m_pkiRoot).ConfigureAwait(false); m_clientFixture.Config.TransportQuotas.MaxMessageSize = 4 * 1024 * 1024; - m_url = new Uri(m_uriScheme + "://localhost:" + m_serverFixture.Port.ToString()); + m_url = new Uri(m_uriScheme + "://localhost:" + m_serverFixture.Port.ToString(CultureInfo.InvariantCulture)); try { m_session = await m_clientFixture.ConnectAsync(m_url, SecurityPolicies.Basic256Sha256).ConfigureAwait(false); diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs index 5bfeadfa29..b73e471d16 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; using Newtonsoft.Json.Linq; @@ -104,8 +105,8 @@ public class ComplexTypesJsonEncoderTests : ComplexTypesCommon { BuiltInType.Int16, (Int16)(-12345), "-12345", null }, { BuiltInType.UInt32, (UInt32)1234567, "1234567", null }, { BuiltInType.Int32, (Int32)(-12345678), "-12345678", null }, - { BuiltInType.Int64, kInt64Value, Quotes(kInt64Value.ToString()), null }, - { BuiltInType.UInt64, (UInt64)kUInt64Value, Quotes(kUInt64Value.ToString()), null }, + { BuiltInType.Int64, kInt64Value, Quotes(kInt64Value.ToString(CultureInfo.InvariantCulture)), null }, + { BuiltInType.UInt64, (UInt64)kUInt64Value, Quotes(kUInt64Value.ToString(CultureInfo.InvariantCulture)), null }, { BuiltInType.Float, (float)3.14, "3.14", "3.14" }, // TODO: why is JToken.DeepEquals failing here? //{ BuiltInType.Float, float.PositiveInfinity, "Infinity", "Infinity" }, diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs index 61e4973c3e..9bc2e73c05 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolverTests.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Runtime.Serialization; @@ -670,7 +671,7 @@ private object GetRandom(NodeId valueType) { var buildInfo = new BuildInfo() { BuildDate = DataGenerator.GetRandomDateTime(), - BuildNumber = "1.4." + DataGenerator.GetRandomByte().ToString(), + BuildNumber = "1.4." + DataGenerator.GetRandomByte().ToString(CultureInfo.InvariantCulture), ManufacturerName = "OPC Foundation", ProductName = "Complex Type Client", ProductUri = "http://opcfoundation.org/ComplexTypeClient", diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs index cc88123717..4cb3ae1503 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs @@ -89,56 +89,56 @@ public class JsonEncoderTests : EncoderCommon { BuiltInType.Byte, (Byte)0, "0", null, true }, { BuiltInType.Byte, (Byte)88, "88", null }, { BuiltInType.Byte, (Byte)188, "188", null }, - { BuiltInType.Byte, Byte.MinValue, Byte.MinValue.ToString(), null, true}, - { BuiltInType.Byte, Byte.MaxValue, Byte.MaxValue.ToString(), null }, + { BuiltInType.Byte, Byte.MinValue, Byte.MinValue.ToString(CultureInfo.InvariantCulture), null, true}, + { BuiltInType.Byte, Byte.MaxValue, Byte.MaxValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.SByte, (SByte)0, null, null }, { BuiltInType.SByte, (SByte)0, "0", null, true }, { BuiltInType.SByte, (SByte)(-77), "-77", null }, { BuiltInType.SByte, (SByte)(77), "77", null }, - { BuiltInType.SByte, SByte.MaxValue, SByte.MaxValue.ToString(), null }, - { BuiltInType.SByte, SByte.MinValue, SByte.MinValue.ToString(), null }, + { BuiltInType.SByte, SByte.MaxValue, SByte.MaxValue.ToString(CultureInfo.InvariantCulture), null }, + { BuiltInType.SByte, SByte.MinValue, SByte.MinValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.UInt16, (UInt16)0, null, null}, { BuiltInType.UInt16, (UInt16)0, "0", null, true }, { BuiltInType.UInt16, (UInt16)12345, "12345", null }, { BuiltInType.UInt16, (UInt16)44444, "44444", null }, - { BuiltInType.UInt16, UInt16.MinValue, UInt16.MinValue.ToString(), null, true }, - { BuiltInType.UInt16, UInt16.MaxValue, UInt16.MaxValue.ToString(), null }, + { BuiltInType.UInt16, UInt16.MinValue, UInt16.MinValue.ToString(CultureInfo.InvariantCulture), null, true }, + { BuiltInType.UInt16, UInt16.MaxValue, UInt16.MaxValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.Int16, (Int16)0, null, null }, { BuiltInType.Int16, (Int16)0, "0", null, true }, { BuiltInType.Int16, (Int16)(-12345), "-12345", null }, { BuiltInType.Int16, (Int16)12345, "12345", null }, - { BuiltInType.Int16, Int16.MaxValue, Int16.MaxValue.ToString(), null }, - { BuiltInType.Int16, Int16.MinValue, Int16.MinValue.ToString(), null }, + { BuiltInType.Int16, Int16.MaxValue, Int16.MaxValue.ToString(CultureInfo.InvariantCulture), null }, + { BuiltInType.Int16, Int16.MinValue, Int16.MinValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.UInt32, (UInt32)0, null, null }, { BuiltInType.UInt32, (UInt32)0, "0", null, true }, { BuiltInType.UInt32, (UInt32)1234567, "1234567", null }, { BuiltInType.UInt32, (UInt32)4444444, "4444444", null }, - { BuiltInType.UInt32, UInt32.MinValue, UInt32.MinValue.ToString(), null, true }, - { BuiltInType.UInt32, UInt32.MaxValue, UInt32.MaxValue.ToString(), null }, + { BuiltInType.UInt32, UInt32.MinValue, UInt32.MinValue.ToString(CultureInfo.InvariantCulture), null, true }, + { BuiltInType.UInt32, UInt32.MaxValue, UInt32.MaxValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.Int32, 0, null, null }, { BuiltInType.Int32, 0, "0", null, true }, { BuiltInType.Int32, -12345678, "-12345678", null }, { BuiltInType.Int32, 12345678, "12345678", null }, - { BuiltInType.Int32, Int32.MaxValue, Int32.MaxValue.ToString(), null }, - { BuiltInType.Int32, Int32.MinValue, Int32.MinValue.ToString(), null }, + { BuiltInType.Int32, Int32.MaxValue, Int32.MaxValue.ToString(CultureInfo.InvariantCulture), null }, + { BuiltInType.Int32, Int32.MinValue, Int32.MinValue.ToString(CultureInfo.InvariantCulture), null }, { BuiltInType.Int64, (Int64)0, null, null }, { BuiltInType.Int64, (Int64)0, Quotes("0"), null, true }, - { BuiltInType.Int64, kInt64Value, Quotes(kInt64Value.ToString()), null }, - { BuiltInType.Int64, (Int64)kUInt64Value, Quotes(kUInt64Value.ToString()), null }, - { BuiltInType.Int64, Int64.MinValue, Quotes(Int64.MinValue.ToString()), null }, - { BuiltInType.Int64, Int64.MaxValue, Quotes(Int64.MaxValue.ToString()), null }, + { BuiltInType.Int64, kInt64Value, Quotes(kInt64Value.ToString(CultureInfo.InvariantCulture)), null }, + { BuiltInType.Int64, (Int64)kUInt64Value, Quotes(kUInt64Value.ToString(CultureInfo.InvariantCulture)), null }, + { BuiltInType.Int64, Int64.MinValue, Quotes(Int64.MinValue.ToString(CultureInfo.InvariantCulture)), null }, + { BuiltInType.Int64, Int64.MaxValue, Quotes(Int64.MaxValue.ToString(CultureInfo.InvariantCulture)), null }, { BuiltInType.UInt64, (UInt64)0, null, null }, { BuiltInType.UInt64, (UInt64)0, Quotes("0"), null, true }, - { BuiltInType.UInt64, (UInt64)kUInt64Value, Quotes(kUInt64Value.ToString()), null }, - { BuiltInType.UInt64, UInt64.MinValue, Quotes(UInt64.MinValue.ToString()), null, true }, - { BuiltInType.UInt64, UInt64.MaxValue, Quotes(UInt64.MaxValue.ToString()), null }, + { BuiltInType.UInt64, (UInt64)kUInt64Value, Quotes(kUInt64Value.ToString(CultureInfo.InvariantCulture)), null }, + { BuiltInType.UInt64, UInt64.MinValue, Quotes(UInt64.MinValue.ToString(CultureInfo.InvariantCulture)), null, true }, + { BuiltInType.UInt64, UInt64.MaxValue, Quotes(UInt64.MaxValue.ToString(CultureInfo.InvariantCulture)), null }, { BuiltInType.Float, (Single)0, null, null}, { BuiltInType.Float, (Single)0, "0", null, true}, From e5391892f61aca26346dfab52159165ef6f61e21 Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Tue, 20 Feb 2024 19:52:27 +0100 Subject: [PATCH 18/19] cleanup --- Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 86d5244fbf..c0c9c3e1e7 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -501,7 +501,7 @@ public void PopNamespace() } private static readonly char[] m_specialChars = new char[] { s_quotation, s_backslash, '\n', '\r', '\t', '\b', '\f', }; - private static readonly char[] m_substitution = new char[] { '\"', '\\', 'n', 'r', 't', 'b', 'f' }; + private static readonly char[] m_substitution = new char[] { s_quotation, s_backslash, 'n', 'r', 't', 'b', 'f' }; #if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER /// From f697283d5b7d6fce2c040becdcd9ebcd8cdfc9eb Mon Sep 17 00:00:00 2001 From: Bhupendra Naphade Date: Wed, 21 Feb 2024 09:22:45 +0100 Subject: [PATCH 19/19] fixed WriteSpan and test conditional --- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 2 +- .../Types/Encoders/JsonEncoderBenchmarks.cs | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index c0c9c3e1e7..43e4e9235f 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -554,7 +554,7 @@ private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int ind { if (lastOffset < index - 2) { - m_writer.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); + m_writer.Write(valueSpan.Slice(lastOffset, index - lastOffset)); } else { diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs index 25ab1aaef0..fa8f0926a2 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderBenchmarks.cs @@ -725,7 +725,7 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -742,7 +742,7 @@ private void EscapeStringSpan(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -759,7 +759,7 @@ private void EscapeStringSpan(string value) } else if (lastOffset < charSpan.Length) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -787,7 +787,7 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -805,7 +805,7 @@ private void EscapeStringSpanChars(string value) { if (lastOffset < i - 1) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -827,7 +827,7 @@ private void EscapeStringSpanChars(string value) if (lastOffset == 0) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan); #else m_streamWriter.Write(value); @@ -835,7 +835,7 @@ private void EscapeStringSpanChars(string value) } else if (lastOffset < charSpan.Length) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -848,7 +848,7 @@ private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int ind { if (lastOffset < index - 2) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset)); #else m_streamWriter.Write(valueSpan.Slice(lastOffset, index - lastOffset).ToString()); @@ -901,7 +901,7 @@ private void EscapeStringSpanCharsInline(string value) if (lastOffset == 0) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan); #else m_streamWriter.Write(value); @@ -927,7 +927,7 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -942,7 +942,7 @@ private void EscapeStringSpanIndex(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -960,7 +960,7 @@ private void EscapeStringSpanIndex(string value) } else if (lastOffset < charSpan.Length) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString()); @@ -981,7 +981,7 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -996,7 +996,7 @@ private void EscapeStringSpanDict(string value) { if (lastOffset < i) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, i - lastOffset).ToString()); @@ -1014,7 +1014,7 @@ private void EscapeStringSpanDict(string value) } else if (lastOffset < charSpan.Length) { -#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER +#if NETCOREAPP2_1_OR_GREATER m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset)); #else m_streamWriter.Write(charSpan.Slice(lastOffset, charSpan.Length - lastOffset).ToString());