From 7a93892d030e52270d320c1fb760cbad45bff927 Mon Sep 17 00:00:00 2001 From: Jon Hanna Date: Mon, 9 Jan 2017 03:42:40 +0000 Subject: [PATCH] Optimise WriteEscapedJavaScriptString Skip attempting write when string is empty. FirstCharToEscape was introduced for the async version and gives a faster means to identify the first block before any escaping, so may as well use it here too. Conversely, WriteEscapedStringThroughBuffer was introduced to share code between the sync and async versions, but later optimisations meant it was no longer used by the async. Inline the code back again. --- .../Utilities/JavaScriptUtils.cs | 284 +++++++++--------- 1 file changed, 146 insertions(+), 138 deletions(-) diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index b3da74b4..02e39aed 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -30,6 +30,7 @@ using System.Threading; using System.Threading.Tasks; #endif using System.Collections.Generic; +using System.Diagnostics; #if NET20 using Newtonsoft.Json.Utilities.LinqBridge; #else @@ -149,137 +150,158 @@ namespace Newtonsoft.Json.Utilities writer.Write(delimiter); } - if (s != null) + if (!string.IsNullOrEmpty(s)) { - int lastWritePosition = 0; - - for (int i = 0; i < s.Length; i++) + int lastWritePosition = FirstCharToEscape(s, charEscapeFlags, stringEscapeHandling); + if (lastWritePosition == -1) { - char c = s[i]; - - if (c < charEscapeFlags.Length && !charEscapeFlags[c]) - { - continue; - } - - string escapedValue; - - switch (c) - { - case '\t': - escapedValue = @"\t"; - break; - case '\n': - escapedValue = @"\n"; - break; - case '\r': - escapedValue = @"\r"; - break; - case '\f': - escapedValue = @"\f"; - break; - case '\b': - escapedValue = @"\b"; - break; - case '\\': - escapedValue = @"\\"; - break; - case '\u0085': // Next Line - escapedValue = @"\u0085"; - break; - case '\u2028': // Line Separator - escapedValue = @"\u2028"; - break; - case '\u2029': // Paragraph Separator - escapedValue = @"\u2029"; - break; - default: - if (c < charEscapeFlags.Length || stringEscapeHandling == StringEscapeHandling.EscapeNonAscii) - { - if (c == '\'' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) - { - escapedValue = @"\'"; - } - else if (c == '"' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) - { - escapedValue = @"\"""; - } - else - { - if (writeBuffer == null || writeBuffer.Length < UnicodeTextLength) - { - writeBuffer = BufferUtils.EnsureBufferSize(bufferPool, UnicodeTextLength, writeBuffer); - } - - StringUtils.ToCharAsUnicode(c, writeBuffer); - - // slightly hacky but it saves multiple conditions in if test - escapedValue = EscapedUnicodeText; - } - } - else - { - escapedValue = null; - } - break; - } - - if (escapedValue == null) - { - continue; - } - - bool isEscapedUnicodeText = string.Equals(escapedValue, EscapedUnicodeText); - - if (i > lastWritePosition) - { - int length = i - lastWritePosition + ((isEscapedUnicodeText) ? UnicodeTextLength : 0); - int start = (isEscapedUnicodeText) ? UnicodeTextLength : 0; - - if (writeBuffer == null || writeBuffer.Length < length) - { - char[] newBuffer = BufferUtils.RentBuffer(bufferPool, length); - - // the unicode text is already in the buffer - // copy it over when creating new buffer - if (isEscapedUnicodeText) - { - Array.Copy(writeBuffer, newBuffer, UnicodeTextLength); - } - - BufferUtils.ReturnBuffer(bufferPool, writeBuffer); - - writeBuffer = newBuffer; - } - - s.CopyTo(lastWritePosition, writeBuffer, start, length - start); - - // write unchanged chars before writing escaped text - writer.Write(writeBuffer, start, length - start); - } - - lastWritePosition = i + 1; - if (!isEscapedUnicodeText) - { - writer.Write(escapedValue); - } - else - { - writer.Write(writeBuffer, 0, UnicodeTextLength); - } - } - - if (lastWritePosition == 0) - { - // no escaped text, write entire string writer.Write(s); } else { - int length = WriteEscapedStringThroughBuffer(s, lastWritePosition, bufferPool, ref writeBuffer); + if (lastWritePosition != 0) + { + if (writeBuffer == null || writeBuffer.Length < lastWritePosition) + { + writeBuffer = BufferUtils.EnsureBufferSize(bufferPool, lastWritePosition, writeBuffer); + } - // write remaining text - writer.Write(writeBuffer, 0, length); + // write unchanged chars at start of text. + s.CopyTo(0, writeBuffer, 0, lastWritePosition); + writer.Write(writeBuffer, 0, lastWritePosition); + } + + int length; + for (int i = lastWritePosition; i < s.Length; i++) + { + char c = s[i]; + + if (c < charEscapeFlags.Length && !charEscapeFlags[c]) + { + continue; + } + + string escapedValue; + + switch (c) + { + case '\t': + escapedValue = @"\t"; + break; + case '\n': + escapedValue = @"\n"; + break; + case '\r': + escapedValue = @"\r"; + break; + case '\f': + escapedValue = @"\f"; + break; + case '\b': + escapedValue = @"\b"; + break; + case '\\': + escapedValue = @"\\"; + break; + case '\u0085': // Next Line + escapedValue = @"\u0085"; + break; + case '\u2028': // Line Separator + escapedValue = @"\u2028"; + break; + case '\u2029': // Paragraph Separator + escapedValue = @"\u2029"; + break; + default: + if (c < charEscapeFlags.Length || stringEscapeHandling == StringEscapeHandling.EscapeNonAscii) + { + if (c == '\'' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) + { + escapedValue = @"\'"; + } + else if (c == '"' && stringEscapeHandling != StringEscapeHandling.EscapeHtml) + { + escapedValue = @"\"""; + } + else + { + if (writeBuffer == null || writeBuffer.Length < UnicodeTextLength) + { + writeBuffer = BufferUtils.EnsureBufferSize(bufferPool, UnicodeTextLength, writeBuffer); + } + + StringUtils.ToCharAsUnicode(c, writeBuffer); + + // slightly hacky but it saves multiple conditions in if test + escapedValue = EscapedUnicodeText; + } + } + else + { + escapedValue = null; + } + break; + } + + if (escapedValue == null) + { + continue; + } + + bool isEscapedUnicodeText = string.Equals(escapedValue, EscapedUnicodeText); + + if (i > lastWritePosition) + { + length = i - lastWritePosition + ((isEscapedUnicodeText) ? UnicodeTextLength : 0); + int start = (isEscapedUnicodeText) ? UnicodeTextLength : 0; + + if (writeBuffer == null || writeBuffer.Length < length) + { + char[] newBuffer = BufferUtils.RentBuffer(bufferPool, length); + + // the unicode text is already in the buffer + // copy it over when creating new buffer + if (isEscapedUnicodeText) + { + Array.Copy(writeBuffer, newBuffer, UnicodeTextLength); + } + + BufferUtils.ReturnBuffer(bufferPool, writeBuffer); + + writeBuffer = newBuffer; + } + + s.CopyTo(lastWritePosition, writeBuffer, start, length - start); + + // write unchanged chars before writing escaped text + writer.Write(writeBuffer, start, length - start); + } + + lastWritePosition = i + 1; + if (!isEscapedUnicodeText) + { + writer.Write(escapedValue); + } + else + { + writer.Write(writeBuffer, 0, UnicodeTextLength); + } + } + + Debug.Assert(lastWritePosition != 0); + length = s.Length - lastWritePosition; + if (length > 0) + { + if (writeBuffer == null || writeBuffer.Length < length) + { + writeBuffer = BufferUtils.EnsureBufferSize(bufferPool, length, writeBuffer); + } + + s.CopyTo(lastWritePosition, writeBuffer, 0, length); + + // write remaining text + writer.Write(writeBuffer, 0, length); + } } } @@ -290,20 +312,6 @@ namespace Newtonsoft.Json.Utilities } } - private static int WriteEscapedStringThroughBuffer(string s, int lastWritePosition, IArrayPool bufferPool, ref char[] writeBuffer) - { - int length = s.Length - lastWritePosition; - - if (writeBuffer == null || writeBuffer.Length < length) - { - writeBuffer = BufferUtils.EnsureBufferSize(bufferPool, length, writeBuffer); - } - - s.CopyTo(lastWritePosition, writeBuffer, 0, length); - - return length; - } - public static string ToEscapedJavaScriptString(string value, char delimiter, bool appendDelimiters, StringEscapeHandling stringEscapeHandling) { bool[] charEscapeFlags = GetCharEscapeFlags(stringEscapeHandling, delimiter);