From 6d109a4c55fccf5a48dd8049fcb32a7fdaf133dd Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Mon, 22 Apr 2013 23:55:21 +1200 Subject: [PATCH] -Reduced string allocations --- .../JsonTextWriterTest.cs | 3 +- Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 3 +- .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/JsonTextReader.cs | 49 ++++--- Src/Newtonsoft.Json/JsonTextWriter.cs | 12 +- .../Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Utilities/ConvertUtils.cs | 134 +++++++++++++----- .../Utilities/JavaScriptUtils.cs | 50 +++++-- 8 files changed, 176 insertions(+), 79 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs index b0e3c52d..7cf1280d 100644 --- a/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonTextWriterTest.cs @@ -1183,7 +1183,8 @@ _____'propertyName': NaN Console.WriteLine("Position: " + (int)c); StringWriter swNew = new StringWriter(); - JavaScriptUtils.WriteEscapedJavaScriptString(swNew, c.ToString(), '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default); + char[] buffer = null; + JavaScriptUtils.WriteEscapedJavaScriptString(swNew, c.ToString(), '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default, ref buffer); StringWriter swOld = new StringWriter(); WriteEscapedJavaScriptStringOld(swOld, c.ToString(), '"', true); diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 2aa87889..90961507 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -324,7 +324,8 @@ If attributes are not mentioned, default values are used in each case. { using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(text) ?? 16)) { - JavaScriptUtils.WriteEscapedJavaScriptString(w, text, '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default); + char[] buffer = null; + JavaScriptUtils.WriteEscapedJavaScriptString(w, text, '"', true, JavaScriptUtils.DoubleQuoteCharEscapeFlags, StringEscapeHandling.Default, ref buffer); } } diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index ee097193..40034acc 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.4.16021")] +[assembly: AssemblyFileVersion("5.0.4.16022")] diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 0666cca4..5799c317 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -648,6 +648,11 @@ namespace Newtonsoft.Json if (ReadData(true) == 0) return; } + else + { + _charPos = charPos - 1; + return; + } break; case '-': case '+': @@ -1187,7 +1192,7 @@ namespace Newtonsoft.Json } else { - numberValue = ConvertUtils.IntParseFast(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); + numberValue = ConvertUtils.Int32Parse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length); } numberType = JsonToken.Integer; @@ -1238,11 +1243,17 @@ namespace Newtonsoft.Json } else { - string number = _stringReference.ToString(); - - // it's faster to do 3 indexof with single characters than an indexofany - if (number.IndexOf('.') != -1 || number.IndexOf('E') != -1 || number.IndexOf('e') != -1) + long value; + ParseResult parseResult = ConvertUtils.Int64TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out value); + if (parseResult == ParseResult.Success) { + numberValue = value; + numberType = JsonToken.Integer; + } + else if (parseResult == ParseResult.Invalid) + { + string number = _stringReference.ToString(); + if (_floatParseHandling == FloatParseHandling.Decimal) numberValue = decimal.Parse(number, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture); else @@ -1250,24 +1261,20 @@ namespace Newtonsoft.Json numberType = JsonToken.Float; } + else if (parseResult == ParseResult.Overflow) + { +#if !(NET20 || NET35 || SILVERLIGHT || PORTABLE40 || PORTABLE) + string number = _stringReference.ToString(); + numberValue = BigInteger.Parse(number, CultureInfo.InvariantCulture); + numberType = JsonToken.Integer; +#else + // todo - validate number was a valid integer to make sure overflow was the reason for failure + throw JsonReaderException.Create(this, "JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); +#endif + } else { - long value; - if (long.TryParse(number, out value)) - { - numberValue = value; - } - else - { -#if !(NET20 || NET35 || SILVERLIGHT || PORTABLE40 || PORTABLE) - numberValue = BigInteger.Parse(number, CultureInfo.InvariantCulture); -#else - // todo - validate number was a valid integer to make sure overflow was the reason for failure - throw JsonReaderException.Create(this, "JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number)); -#endif - } - - numberType = JsonToken.Integer; + throw JsonReaderException.Create(this, "Unknown error parsing integer."); } } } diff --git a/Src/Newtonsoft.Json/JsonTextWriter.cs b/Src/Newtonsoft.Json/JsonTextWriter.cs index 53287afd..f3ff20d8 100644 --- a/Src/Newtonsoft.Json/JsonTextWriter.cs +++ b/Src/Newtonsoft.Json/JsonTextWriter.cs @@ -214,7 +214,7 @@ namespace Newtonsoft.Json { InternalWritePropertyName(name); - JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName, _charEscapeFlags, StringEscapeHandling); + WriteEscapedString(name); _writer.Write(':'); } @@ -230,7 +230,7 @@ namespace Newtonsoft.Json if (escape) { - JavaScriptUtils.WriteEscapedJavaScriptString(_writer, name, _quoteChar, _quoteName, _charEscapeFlags, StringEscapeHandling); + WriteEscapedString(name); } else { @@ -364,7 +364,13 @@ namespace Newtonsoft.Json if (value == null) WriteValueInternal(JsonConvert.Null, JsonToken.Null); else - JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true, _charEscapeFlags, StringEscapeHandling); + WriteEscapedString(value); + } + + private void WriteEscapedString(string value) + { + EnsureWriteBuffer(); + JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, true, _charEscapeFlags, StringEscapeHandling, ref _writeBuffer); } /// diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index ca6725d2..72bc6abf 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -92,6 +92,6 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.4.16021")] +[assembly: AssemblyFileVersion("5.0.4.16022")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs index da127ad1..d0c54ce2 100644 --- a/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ConvertUtils.cs @@ -98,6 +98,14 @@ namespace Newtonsoft.Json.Utilities public PrimitiveTypeCode TypeCode { get; set; } } + internal enum ParseResult + { + None, + Success, + Overflow, + Invalid + } + internal static class ConvertUtils { private static readonly Dictionary TypeCodeMap = @@ -205,7 +213,7 @@ namespace Newtonsoft.Json.Utilities #if !(NETFX_CORE || PORTABLE) public static TypeInformation GetTypeInformation(IConvertible convertable) { - TypeInformation typeInformation = PrimitiveTypeCodes[(int) convertable.GetTypeCode()]; + TypeInformation typeInformation = PrimitiveTypeCodes[(int)convertable.GetTypeCode()]; return typeInformation; } #endif @@ -224,7 +232,7 @@ namespace Newtonsoft.Json.Utilities public static TimeSpan ParseTimeSpan(string input) { #if !(NET35 || NET20 || PORTABLE40) - return TimeSpan.Parse((string) input, CultureInfo.InvariantCulture); + return TimeSpan.Parse((string)input, CultureInfo.InvariantCulture); #else return TimeSpan.Parse((string)input); #endif @@ -316,14 +324,14 @@ namespace Newtonsoft.Json.Utilities public static object FromBigInteger(BigInteger i, Type targetType) { - if (targetType == typeof (decimal)) - return (decimal) i; - if (targetType == typeof (double)) - return (double) i; - if (targetType == typeof (float)) - return (float) i; - if (targetType == typeof (ulong)) - return (ulong) i; + if (targetType == typeof(decimal)) + return (decimal)i; + if (targetType == typeof(double)) + return (double)i; + if (targetType == typeof(float)) + return (float)i; + if (targetType == typeof(ulong)) + return (ulong)i; try { @@ -337,6 +345,7 @@ namespace Newtonsoft.Json.Utilities #endif #region Convert + /// /// Converts the value to the specified type. /// @@ -377,18 +386,18 @@ namespace Newtonsoft.Json.Utilities #endif if (initialValue is byte[] && targetType == typeof(Guid)) - return new Guid((byte[]) initialValue); + return new Guid((byte[])initialValue); if (initialValue is string) { - if (targetType == typeof (Guid)) - return new Guid((string) initialValue); - if (targetType == typeof (Uri)) - return new Uri((string) initialValue, UriKind.RelativeOrAbsolute); + if (targetType == typeof(Guid)) + return new Guid((string)initialValue); + if (targetType == typeof(Uri)) + return new Uri((string)initialValue, UriKind.RelativeOrAbsolute); if (targetType == typeof(TimeSpan)) - return ParseTimeSpan((string) initialValue); + return ParseTimeSpan((string)initialValue); if (typeof(Type).IsAssignableFrom(targetType)) - return Type.GetType((string) initialValue, true); + return Type.GetType((string)initialValue, true); } #if !(NET20 || NET35 || SILVERLIGHT || PORTABLE40 || PORTABLE) @@ -431,7 +440,7 @@ namespace Newtonsoft.Json.Utilities { if (ReflectionUtils.IsNullable(targetType)) return EnsureTypeAssignable(null, initialType, targetType); - + throw new Exception("Can not convert null {0} into non-nullable {1}.".FormatWith(CultureInfo.InvariantCulture, initialType, targetType)); } #endif @@ -446,6 +455,7 @@ namespace Newtonsoft.Json.Utilities #endregion #region TryConvert + /// /// Converts the value to the specified type. /// @@ -469,9 +479,11 @@ namespace Newtonsoft.Json.Utilities return false; } } + #endregion #region ConvertOrCast + /// /// Converts the value to the specified type. If the value is unable to be converted, the /// value is checked whether it assignable to the specified type. @@ -498,6 +510,7 @@ namespace Newtonsoft.Json.Utilities return EnsureTypeAssignable(initialValue, ReflectionUtils.GetObjectType(initialValue), targetType); } + #endregion private static object EnsureTypeAssignable(object value, Type initialType, Type targetType) @@ -567,12 +580,12 @@ namespace Newtonsoft.Json.Utilities } } - public static int IntParseFast(char[] value, int start, int length) + public static int Int32Parse(char[] chars, int start, int length) { if (length == 0) throw new FormatException("Input string was not in a correct format."); - bool isNegative = (value[start] == '-'); + bool isNegative = (chars[start] == '-'); if (isNegative) { @@ -584,27 +597,76 @@ namespace Newtonsoft.Json.Utilities length--; } - checked + int result = 0; + int end = start + length; + + for (int i = start; i < end; i++) { - int result = 0; - int end = start + length; - for (int i = start; i < end; i++) - { - char c = value[i]; + int c = chars[i] - '0'; - if (c < '0' || c > '9') - throw new FormatException("Input string was not in a correct format."); + if (c < 0 || c > 9) + throw new FormatException("Input string was not in a correct format."); - result = 10*result + -(c - 48); - } + int newValue = (10*result) - c; - // go from negative to positive to avoids overflow - // negative can be slightly bigger than positive - if (!isNegative) - result = -result; + // overflow has caused the number to loop around + if (newValue > result) + throw new OverflowException(); - return result; + result = newValue; } + + // go from negative to positive to avoids overflow + // negative can be slightly bigger than positive + if (!isNegative) + result = -result; + + return result; + } + + public static ParseResult Int64TryParse(char[] chars, int start, int length, out long value) + { + value = 0; + + if (length == 0) + return ParseResult.Invalid; + + bool isNegative = (chars[start] == '-'); + + if (isNegative) + { + // text just a negative sign + if (length == 1) + return ParseResult.Invalid; + + start++; + length--; + } + + int end = start + length; + + for (int i = start; i < end; i++) + { + int c = chars[i] - '0'; + + if (c < 0 || c > 9) + return ParseResult.Invalid; + + long newValue = (10*value) - c; + + // overflow has caused the number to loop around + if (newValue > value) + return ParseResult.Overflow; + + value = newValue; + } + + // go from negative to positive to avoids overflow + // negative can be slightly bigger than positive + if (!isNegative) + value = -value; + + return ParseResult.Success; } } -} +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs index 17333ab9..15f95e1a 100644 --- a/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/JavaScriptUtils.cs @@ -71,7 +71,8 @@ namespace Newtonsoft.Json.Utilities private const string EscapedUnicodeText = "!"; - public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters, bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling) + public static void WriteEscapedJavaScriptString(TextWriter writer, string s, char delimiter, bool appendDelimiters, + bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling, ref char[] writeBuffer) { // leading delimiter if (appendDelimiters) @@ -79,8 +80,6 @@ namespace Newtonsoft.Json.Utilities if (s != null) { - char[] chars = null; - char[] unicodeBuffer = null; int lastWritePosition = 0; for (int i = 0; i < s.Length; i++) @@ -134,10 +133,10 @@ namespace Newtonsoft.Json.Utilities } else { - if (unicodeBuffer == null) - unicodeBuffer = new char[6]; + if (writeBuffer == null) + writeBuffer = new char[6]; - StringUtils.ToCharAsUnicode(c, unicodeBuffer); + StringUtils.ToCharAsUnicode(c, writeBuffer); // slightly hacky but it saves multiple conditions in if test escapedValue = EscapedUnicodeText; @@ -153,20 +152,36 @@ namespace Newtonsoft.Json.Utilities if (escapedValue == null) continue; + bool isEscapedUnicodeText = string.Equals(escapedValue, EscapedUnicodeText); + if (i > lastWritePosition) { - if (chars == null) - chars = s.ToCharArray(); + int length = i - lastWritePosition + ((isEscapedUnicodeText) ? 6 : 0); + int start = (isEscapedUnicodeText) ? 6 : 0; + + if (writeBuffer == null || writeBuffer.Length < length) + { + char[] newBuffer = new char[length]; + + // the unicode text is already in the buffer + // copy it over when creating new buffer + if (isEscapedUnicodeText) + Array.Copy(writeBuffer, newBuffer, 6); + + writeBuffer = newBuffer; + } + + s.CopyTo(lastWritePosition, writeBuffer, start, length - start); // write unchanged chars before writing escaped text - writer.Write(chars, lastWritePosition, i - lastWritePosition); + writer.Write(writeBuffer, start, length - start); } lastWritePosition = i + 1; - if (!string.Equals(escapedValue, EscapedUnicodeText)) + if (!isEscapedUnicodeText) writer.Write(escapedValue); else - writer.Write(unicodeBuffer); + writer.Write(writeBuffer, 0, 6); } if (lastWritePosition == 0) @@ -176,11 +191,15 @@ namespace Newtonsoft.Json.Utilities } else { - if (chars == null) - chars = s.ToCharArray(); + int length = s.Length - lastWritePosition; + + if (writeBuffer == null || writeBuffer.Length < length) + writeBuffer = new char[length]; + + s.CopyTo(lastWritePosition, writeBuffer, 0, length); // write remaining text - writer.Write(chars, lastWritePosition, s.Length - lastWritePosition); + writer.Write(writeBuffer, 0, length); } } @@ -193,7 +212,8 @@ namespace Newtonsoft.Json.Utilities { using (StringWriter w = StringUtils.CreateStringWriter(StringUtils.GetLength(value) ?? 16)) { - WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters, (delimiter == '"') ? DoubleQuoteCharEscapeFlags : SingleQuoteCharEscapeFlags, StringEscapeHandling.Default); + char[] buffer = null; + WriteEscapedJavaScriptString(w, value, delimiter, appendDelimiters, (delimiter == '"') ? DoubleQuoteCharEscapeFlags : SingleQuoteCharEscapeFlags, StringEscapeHandling.Default, ref buffer); return w.ToString(); } }