-Performance

This commit is contained in:
James Newton-King
2014-09-28 16:19:18 +13:00
parent 21eb43e3b3
commit 1337c4f7f7
19 changed files with 371 additions and 64 deletions
+25 -3
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -15,14 +16,35 @@ namespace Newtonsoft.Json.TestConsole
Console.ReadKey();
Console.WriteLine("Doing stuff...");
PerformanceTests t = new PerformanceTests();
//PerformanceTests t = new PerformanceTests();
//t.DeserializeLargeJson();
//t.BenchmarkSerializeMethod(PerformanceTests.SerializeMethod.JsonNet, new { hello = "world" });
t.DeserializeLargeJson();
WriteLargeJson();
Console.WriteLine();
Console.WriteLine("Finished");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
public static void WriteLargeJson()
{
var json = System.IO.File.ReadAllText("large.json");
IList<PerformanceTests.RootObject> o = JsonConvert.DeserializeObject<IList<PerformanceTests.RootObject>>(json);
Console.WriteLine("Press any key to start serialize");
Console.ReadKey();
for (int i = 0; i < 100; i++)
{
using (StreamWriter file = System.IO.File.CreateText("largewrite.json"))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
serializer.Serialize(file, o);
}
}
}
}
}
@@ -33,4 +33,4 @@ using System.Runtime.InteropServices;
// by using the '*' as shown below:
// [assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyFileVersion("6.0.5.17725")]
[assembly: AssemblyFileVersion("6.0.5.17728")]
@@ -82,6 +82,60 @@ namespace Newtonsoft.Json.Tests
}
}
public class NameTableTestClass
{
public string Value { get; set; }
}
public class NameTableTestClassConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
reader.Read();
reader.Read();
JsonTextReader jsonTextReader = (JsonTextReader)reader;
Assert.IsNotNull(jsonTextReader.NameTable);
string s = serializer.Deserialize<string>(reader);
Assert.AreEqual("hi", s);
Assert.IsNotNull(jsonTextReader.NameTable);
NameTableTestClass o = new NameTableTestClass
{
Value = s
};
return o;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(NameTableTestClass);
}
}
[Test]
public void NameTableTest()
{
StringReader sr = new StringReader("{'property':'hi'}");
JsonTextReader jsonTextReader = new JsonTextReader(sr);
Assert.IsNull(jsonTextReader.NameTable);
JsonSerializer serializer = new JsonSerializer();
serializer.Converters.Add(new NameTableTestClassConverter());
NameTableTestClass o = serializer.Deserialize<NameTableTestClass>(jsonTextReader);
Assert.IsNull(jsonTextReader.NameTable);
Assert.AreEqual("hi", o.Value);
}
[Test]
public void DefaultSettings_Example()
{
+13 -17
View File
@@ -160,7 +160,7 @@ namespace Newtonsoft.Json.Tests
public Guid guid { get; set; }
public bool isActive { get; set; }
public string balance { get; set; }
public string picture { get; set; }
public Uri picture { get; set; }
public int age { get; set; }
public string eyeColor { get; set; }
public string name { get; set; }
@@ -170,7 +170,7 @@ namespace Newtonsoft.Json.Tests
public string phone { get; set; }
public string address { get; set; }
public string about { get; set; }
public string registered { get; set; }
public DateTime registered { get; set; }
public double latitude { get; set; }
public decimal longitude { get; set; }
public List<string> tags { get; set; }
@@ -182,15 +182,9 @@ namespace Newtonsoft.Json.Tests
[Test]
public void DeserializeLargeJson()
{
for (int i = 0; i < 5; i++)
{
using (var fs = System.IO.File.OpenText("large.json"))
using (JsonTextReader jsonTextReader = new JsonTextReader(fs))
{
JsonSerializer serializer = new JsonSerializer();
serializer.Deserialize<IList<RootObject>>(jsonTextReader);
}
}
var json = System.IO.File.ReadAllText("large.json");
BenchmarkDeserializeMethod<IList<RootObject>>(SerializeMethod.JsonNet, json, 100, false);
}
[Test]
@@ -812,15 +806,18 @@ If attributes are not mentioned, default values are used in each case.
#endregion
#region Deserialize
public void BenchmarkDeserializeMethod<T>(SerializeMethod method, object json)
public void BenchmarkDeserializeMethod<T>(SerializeMethod method, object json, int? iterations = null, bool warmUp = true)
{
Deserialize<T>(method, json);
if (warmUp)
Deserialize<T>(method, json);
Stopwatch timed = new Stopwatch();
timed.Start();
iterations = iterations ?? Iterations;
T value = default(T);
for (int x = 0; x < Iterations; x++)
for (int x = 0; x < iterations.Value; x++)
{
value = Deserialize<T>(method, json);
}
@@ -950,15 +947,14 @@ If attributes are not mentioned, default values are used in each case.
public T DeserializeWebExtensions<T>(string json)
{
JavaScriptSerializer ser = new JavaScriptSerializer();
JavaScriptSerializer ser = new JavaScriptSerializer { MaxJsonLength = int.MaxValue };
return ser.Deserialize<T>(json);
}
public T DeserializeDataContractJson<T>(string json)
{
DataContractJsonSerializer dataContractSerializer
= new DataContractJsonSerializer(typeof(T));
DataContractJsonSerializer dataContractSerializer = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
@@ -74,4 +74,4 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyFileVersion("6.0.5.17725")]
[assembly: AssemblyFileVersion("6.0.5.17728")]
@@ -36,7 +36,6 @@ using Newtonsoft.Json.Utilities;
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
#endif
namespace Newtonsoft.Json.Converters
+5 -3
View File
@@ -355,15 +355,17 @@ namespace Newtonsoft.Json
internal static string ToString(Guid value, char quoteChar)
{
string text = null;
string text;
string qc;
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
text = value.ToString("D", CultureInfo.InvariantCulture);
qc = quoteChar.ToString(CultureInfo.InvariantCulture);
#else
text = value.ToString("D");
qc = quoteChar.ToString();
#endif
return quoteChar + text + quoteChar;
return qc + text + qc;
}
/// <summary>
+12
View File
@@ -778,6 +778,14 @@ namespace Newtonsoft.Json
{
previousDateFormatString = null;
}
JsonTextReader textReader = reader as JsonTextReader;
if (textReader != null)
{
DefaultContractResolver resolver = _contractResolver as DefaultContractResolver;
if (resolver != null)
textReader.NameTable = resolver.GetState().NameTable;
}
}
private void ResetReader(JsonReader reader, CultureInfo previousCulture, DateTimeZoneHandling? previousDateTimeZoneHandling, DateParseHandling? previousDateParseHandling, FloatParseHandling? previousFloatParseHandling, int? previousMaxDepth, string previousDateFormatString)
@@ -795,6 +803,10 @@ namespace Newtonsoft.Json
reader.MaxDepth = previousMaxDepth;
if (_dateFormatStringSet)
reader.DateFormatString = previousDateFormatString;
JsonTextReader textReader = reader as JsonTextReader;
if (textReader != null)
textReader.NameTable = null;
}
/// <summary>
+16 -3
View File
@@ -29,7 +29,6 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.IO;
using System.Xml;
using System.Globalization;
using Newtonsoft.Json.Utilities;
@@ -65,6 +64,7 @@ namespace Newtonsoft.Json
private bool _isEndOfFile;
private StringBuffer _buffer;
private StringReference _stringReference;
internal PropertyNameTable NameTable;
/// <summary>
/// Initializes a new instance of the <see cref="JsonReader"/> class with the specified <see cref="TextReader"/>.
@@ -174,7 +174,7 @@ namespace Newtonsoft.Json
private void ShiftBufferIfNeeded()
{
// once in the last 10% of the buffer shift the remainling content to the start to avoid
// once in the last 10% of the buffer shift the remaining content to the start to avoid
// unnessesarly increasing the buffer size when reading numbers/strings
int length = _chars.Length;
if (length - _charPos <= length * 0.1)
@@ -844,7 +844,20 @@ namespace Newtonsoft.Json
throw JsonReaderException.Create(this, "Invalid property identifier character: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos]));
}
string propertyName = _stringReference.ToString();
string propertyName;
if (NameTable != null)
{
propertyName = NameTable.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length);
// no match in name table
if (propertyName == null)
propertyName = _stringReference.ToString();
}
else
{
propertyName = _stringReference.ToString();
}
EatWhitespace(false);
+43 -9
View File
@@ -49,6 +49,7 @@ namespace Newtonsoft.Json
private bool _quoteName;
private bool[] _charEscapeFlags;
private char[] _writeBuffer;
private char[] _indentChars;
private Base64Encoder Base64Encoder
{
@@ -98,7 +99,14 @@ namespace Newtonsoft.Json
public char IndentChar
{
get { return _indentChar; }
set { _indentChar = value; }
set
{
if (value != _indentChar)
{
_indentChar = value;
_indentChars = null;
}
}
}
/// <summary>
@@ -266,14 +274,19 @@ namespace Newtonsoft.Json
// levels of indentation multiplied by the indent count
int currentIndentCount = Top * _indentation;
while (currentIndentCount > 0)
if (currentIndentCount > 0)
{
// write up to a max of 10 characters at once to avoid creating too many new strings
int writeCount = Math.Min(currentIndentCount, 10);
if (_indentChars == null)
_indentChars = new string(_indentChar, 10).ToCharArray();
_writer.Write(new string(_indentChar, writeCount));
while (currentIndentCount > 0)
{
int writeCount = Math.Min(currentIndentCount, 10);
currentIndentCount -= writeCount;
_writer.Write(_indentChars, 0, writeCount);
currentIndentCount -= writeCount;
}
}
}
@@ -620,7 +633,18 @@ namespace Newtonsoft.Json
public override void WriteValue(Guid value)
{
InternalWriteValue(JsonToken.String);
WriteValueInternal(JsonConvert.ToString(value, _quoteChar), JsonToken.String);
string text = null;
#if !(NETFX_CORE || PORTABLE40 || PORTABLE)
text = value.ToString("D", CultureInfo.InvariantCulture);
#else
text = value.ToString("D");
#endif
_writer.Write(_quoteChar);
_writer.Write(text);
_writer.Write(_quoteChar);
}
/// <summary>
@@ -630,7 +654,17 @@ namespace Newtonsoft.Json
public override void WriteValue(TimeSpan value)
{
InternalWriteValue(JsonToken.String);
WriteValueInternal(JsonConvert.ToString(value, _quoteChar), JsonToken.String);
string text;
#if (NET35 || NET20)
text = value.ToString();
#else
text = value.ToString(null, CultureInfo.InvariantCulture);
#endif
_writer.Write(_quoteChar);
_writer.Write(text);
_writer.Write(_quoteChar);
}
/// <summary>
@@ -646,7 +680,7 @@ namespace Newtonsoft.Json
else
{
InternalWriteValue(JsonToken.String);
WriteValueInternal(JsonConvert.ToString(value, _quoteChar), JsonToken.String);
WriteEscapedString(value.OriginalString, true);
}
}
#endregion
@@ -234,6 +234,7 @@
<Compile Include="Utilities\ILGeneratorExtensions.cs" />
<Compile Include="Utilities\LateBoundReflectionDelegateFactory.cs" />
<Compile Include="Utilities\MethodCall.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionDelegateFactory.cs" />
<Compile Include="Utilities\ReflectionObject.cs" />
<Compile Include="Utilities\StringReference.cs" />
@@ -245,6 +245,7 @@
<Compile Include="Utilities\ILGeneratorExtensions.cs" />
<Compile Include="Utilities\ImmutableCollectionsUtils.cs" />
<Compile Include="Utilities\LinqBridge.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionDelegateFactory.cs" />
<Compile Include="Utilities\LateBoundReflectionDelegateFactory.cs" />
<Compile Include="Utilities\MethodCall.cs" />
@@ -245,6 +245,7 @@
<Compile Include="Utilities\ILGeneratorExtensions.cs" />
<Compile Include="Utilities\ImmutableCollectionsUtils.cs" />
<Compile Include="Utilities\LinqBridge.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionDelegateFactory.cs" />
<Compile Include="Utilities\LateBoundReflectionDelegateFactory.cs" />
<Compile Include="Utilities\MethodCall.cs" />
@@ -221,6 +221,7 @@
<Compile Include="Utilities\MathUtils.cs" />
<Compile Include="Utilities\MethodCall.cs" />
<Compile Include="Utilities\MiscellaneousUtils.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionDelegateFactory.cs" />
<Compile Include="Utilities\ReflectionObject.cs" />
<Compile Include="Utilities\ReflectionUtils.cs" />
@@ -225,6 +225,7 @@
<Compile Include="Utilities\MathUtils.cs" />
<Compile Include="Utilities\MethodCall.cs" />
<Compile Include="Utilities\MiscellaneousUtils.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionDelegateFactory.cs" />
<Compile Include="Utilities\ReflectionObject.cs" />
<Compile Include="Utilities\ReflectionUtils.cs" />
@@ -213,6 +213,7 @@
<Compile Include="Utilities\DynamicProxyMetaObject.cs" />
<Compile Include="Utilities\DynamicReflectionDelegateFactory.cs" />
<Compile Include="Utilities\DynamicUtils.cs" />
<Compile Include="Utilities\PropertyNameTable.cs" />
<Compile Include="Utilities\ReflectionObject.cs" />
<Compile Include="Utilities\EnumUtils.cs" />
<Compile Include="Utilities\EnumValue.cs" />
@@ -91,5 +91,5 @@ using System.Security;
// by using the '*' as shown below:
[assembly: AssemblyVersion("6.0.0.0")]
[assembly: AssemblyFileVersion("6.0.5.17725")]
[assembly: AssemblyFileVersion("6.0.5.17728")]
[assembly: CLSCompliant(true)]
@@ -83,6 +83,12 @@ namespace Newtonsoft.Json.Serialization
}
}
internal class DefaultContractResolverState
{
public Dictionary<ResolverContractKey, JsonContract> ContractCache;
public PropertyNameTable NameTable = new PropertyNameTable();
}
/// <summary>
/// Used by <see cref="JsonSerializer"/> to resolves a <see cref="JsonContract"/> for a given <see cref="Type"/>.
/// </summary>
@@ -122,10 +128,10 @@ namespace Newtonsoft.Json.Serialization
new RegexConverter()
};
private static Dictionary<ResolverContractKey, JsonContract> _sharedContractCache;
private static readonly object _typeContractCacheLock = new object();
private static readonly object TypeContractCacheLock = new object();
private Dictionary<ResolverContractKey, JsonContract> _instanceContractCache;
private static readonly DefaultContractResolverState _sharedState = new DefaultContractResolverState();
private readonly DefaultContractResolverState _instanceState = new DefaultContractResolverState();
private readonly bool _sharedCache;
/// <summary>
@@ -190,9 +196,9 @@ namespace Newtonsoft.Json.Serialization
/// </summary>
/// <param name="shareCache">
/// If set to <c>true</c> the <see cref="DefaultContractResolver"/> will use a cached shared with other resolvers of the same type.
/// Sharing the cache will significantly performance because expensive reflection will only happen once but could cause unexpected
/// behavior if different instances of the resolver are suppose to produce different results. When set to false it is highly
/// recommended to reuse <see cref="DefaultContractResolver"/> instances with the <see cref="JsonSerializer"/>.
/// Sharing the cache will significantly improve performance with multiple resolver instances because expensive reflection will only
/// happen once. This setting can cause unexpected behavior if different instances of the resolver are suppose to produce different
/// results. When set to false it is highly recommended to reuse <see cref="DefaultContractResolver"/> instances with the <see cref="JsonSerializer"/>.
/// </param>
public DefaultContractResolver(bool shareCache)
{
@@ -208,20 +214,12 @@ namespace Newtonsoft.Json.Serialization
_sharedCache = shareCache;
}
private Dictionary<ResolverContractKey, JsonContract> GetCache()
internal DefaultContractResolverState GetState()
{
if (_sharedCache)
return _sharedContractCache;
return _sharedState;
else
return _instanceContractCache;
}
private void UpdateCache(Dictionary<ResolverContractKey, JsonContract> cache)
{
if (_sharedCache)
_sharedContractCache = cache;
else
_instanceContractCache = cache;
return _instanceState;
}
/// <summary>
@@ -234,24 +232,25 @@ namespace Newtonsoft.Json.Serialization
if (type == null)
throw new ArgumentNullException("type");
DefaultContractResolverState state = GetState();
JsonContract contract;
ResolverContractKey key = new ResolverContractKey(GetType(), type);
Dictionary<ResolverContractKey, JsonContract> cache = GetCache();
Dictionary<ResolverContractKey, JsonContract> cache = state.ContractCache;
if (cache == null || !cache.TryGetValue(key, out contract))
{
contract = CreateContract(type);
// avoid the possibility of modifying the cache dictionary while another thread is accessing it
lock (_typeContractCacheLock)
lock (TypeContractCacheLock)
{
cache = GetCache();
Dictionary<ResolverContractKey, JsonContract> updatedCache =
(cache != null)
? new Dictionary<ResolverContractKey, JsonContract>(cache)
: new Dictionary<ResolverContractKey, JsonContract>();
cache = state.ContractCache;
Dictionary<ResolverContractKey, JsonContract> updatedCache = (cache != null)
? new Dictionary<ResolverContractKey, JsonContract>(cache)
: new Dictionary<ResolverContractKey, JsonContract>();
updatedCache[key] = contract;
UpdateCache(updatedCache);
state.ContractCache = updatedCache;
}
}
@@ -1111,7 +1110,17 @@ namespace Newtonsoft.Json.Serialization
JsonProperty property = CreateProperty(member, memberSerialization);
if (property != null)
{
DefaultContractResolverState state = GetState();
// nametable is not thread-safe for multiple writers
lock (state.NameTable)
{
property.PropertyName = state.NameTable.Add(property.PropertyName);
}
properties.AddProperty(property);
}
}
IList<JsonProperty> orderedProperties = properties.OrderBy(p => p.Order ?? -1).ToList();
@@ -0,0 +1,160 @@
#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System;
namespace Newtonsoft.Json.Utilities
{
internal class PropertyNameTable
{
// used to defeat hashtable DoS attack where someone passes in lots of strings that hash to the same hash code
private static readonly int HashCodeRandomizer;
private int _count;
private Entry[] _entries;
private int _mask = 31;
static PropertyNameTable()
{
HashCodeRandomizer = Environment.TickCount;
}
public PropertyNameTable()
{
_entries = new Entry[_mask + 1];
}
public string Get(char[] key, int start, int length)
{
if (length == 0)
return string.Empty;
int hashCode = length + HashCodeRandomizer;
hashCode += (hashCode << 7) ^ key[start];
int end = start + length;
for (int i = start + 1; i < end; i++)
{
hashCode += (hashCode << 7) ^ key[i];
}
hashCode -= hashCode >> 17;
hashCode -= hashCode >> 11;
hashCode -= hashCode >> 5;
for (Entry entry = _entries[hashCode & _mask]; entry != null; entry = entry.Next)
{
if (entry.HashCode == hashCode && TextEquals(entry.Value, key, start, length))
return entry.Value;
}
return null;
}
public string Add(string key)
{
if (key == null)
throw new ArgumentNullException("key");
int length = key.Length;
if (length == 0)
return string.Empty;
int hashCode = length + HashCodeRandomizer;
for (int i = 0; i < key.Length; i++)
{
hashCode += (hashCode << 7) ^ key[i];
}
hashCode -= hashCode >> 17;
hashCode -= hashCode >> 11;
hashCode -= hashCode >> 5;
for (Entry entry = _entries[hashCode & _mask]; entry != null; entry = entry.Next)
{
if (entry.HashCode == hashCode && entry.Value.Equals(key))
return entry.Value;
}
return AddEntry(key, hashCode);
}
private string AddEntry(string str, int hashCode)
{
int index = hashCode & _mask;
Entry entry = new Entry(str, hashCode, _entries[index]);
_entries[index] = entry;
if (_count++ == _mask)
{
Grow();
}
return entry.Value;
}
private void Grow()
{
Entry[] entries = _entries;
int newMask = (_mask * 2) + 1;
Entry[] newEntries = new Entry[newMask + 1];
for (int i = 0; i < entries.Length; i++)
{
Entry next;
for (Entry entry = entries[i]; entry != null; entry = next)
{
int index = entry.HashCode & newMask;
next = entry.Next;
entry.Next = newEntries[index];
newEntries[index] = entry;
}
}
_entries = newEntries;
_mask = newMask;
}
private static bool TextEquals(string str1, char[] str2, int str2Start, int str2Length)
{
if (str1.Length != str2Length)
return false;
for (int i = 0; i < str1.Length; i++)
{
if (str1[i] != str2[str2Start + i])
return false;
}
return true;
}
private class Entry
{
internal readonly string Value;
internal readonly int HashCode;
internal Entry Next;
internal Entry(string value, int hashCode, Entry next)
{
Value = value;
HashCode = hashCode;
Next = next;
}
}
}
}