-DiscriminatedUnionConverter performance improvements

This commit is contained in:
James Newton-King
2015-03-08 10:15:34 +13:00
parent b1184ef6b5
commit 95e04ba96e
5 changed files with 243 additions and 42 deletions
@@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<UseVSHostingProcess>true</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
+4 -1
View File
@@ -32,6 +32,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using Newtonsoft.Json.Tests;
using Newtonsoft.Json.Tests.Converters;
namespace Newtonsoft.Json.TestConsole
{
@@ -53,8 +54,10 @@ namespace Newtonsoft.Json.TestConsole
//DeserializeLargeJson();
//WriteLargeJson();
//DeserializeJson();
ReadLargeJson();
//ReadLargeJson();
//ReadLargeJsonJavaScriptSerializer();
DiscriminatedUnionConverterTests t = new DiscriminatedUnionConverterTests();
t.SerializePerformance();
Console.WriteLine();
Console.WriteLine("Finished");
@@ -23,6 +23,10 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System.Diagnostics;
using System.Reflection;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Reflection;
#if !(NET35 || NET20 || NETFX_CORE || ASPNETCORE50)
using System;
using System.Collections.Generic;
@@ -83,6 +87,58 @@ namespace Newtonsoft.Json.Tests.Converters
Assert.AreEqual(@"{""Case"":""AUD""}", json);
}
[Test]
public void SerializePerformance()
{
List<Shape> values = new List<Shape>
{
Shape.NewRectangle(10.0, 5.0),
Shape.NewCircle(7.5)
};
string json = JsonConvert.SerializeObject(values, Formatting.Indented);
Stopwatch ts = new Stopwatch();
ts.Start();
for (int i = 0; i < 2000; i++)
{
JsonConvert.SerializeObject(values);
}
ts.Stop();
Console.WriteLine(ts.Elapsed.TotalSeconds);
Console.WriteLine(json);
}
[Test]
public void DeserializePerformance()
{
string json = @"[
{""Case"":""Rectangle"",""Fields"":[10.0,5.0]},
{""Case"":""Rectangle"",""Fields"":[10.0,5.0]},
{""Case"":""Rectangle"",""Fields"":[10.0,5.0]},
{""Case"":""Rectangle"",""Fields"":[10.0,5.0]},
{""Case"":""Rectangle"",""Fields"":[10.0,5.0]}
]";
JsonConvert.DeserializeObject<List<Shape>>(json);
Stopwatch ts = new Stopwatch();
ts.Start();
for (int i = 0; i < 2000; i++)
{
JsonConvert.DeserializeObject<List<Shape>>(json);
}
ts.Stop();
Console.WriteLine(ts.Elapsed.TotalSeconds);
Console.WriteLine(json);
}
[Test]
public void SerializeUnionWithFields()
{
@@ -116,6 +172,82 @@ namespace Newtonsoft.Json.Tests.Converters
Assert.AreEqual(10.0, r.width);
}
public class Union
{
public List<UnionCase> Cases;
public Converter<object, int> TagReader { get; set; }
}
public class UnionCase
{
public int Tag;
public string Name;
public PropertyInfo[] Fields;
public Converter<object, object[]> FieldReader;
public Converter<object[], object> Constructor;
}
private Union CreateUnion(Type t)
{
Union u = new Union();
u.TagReader = FSharpFunc<object, int>.ToConverter(FSharpValue.PreComputeUnionTagReader(t, null));
u.Cases = new List<UnionCase>();
UnionCaseInfo[] cases = FSharpType.GetUnionCases(t, null);
foreach (UnionCaseInfo unionCaseInfo in cases)
{
UnionCase unionCase = new UnionCase();
unionCase.Tag = unionCaseInfo.Tag;
unionCase.Name = unionCaseInfo.Name;
unionCase.Fields = unionCaseInfo.GetFields();
unionCase.FieldReader = FSharpFunc<object, object[]>.ToConverter(FSharpValue.PreComputeUnionReader(unionCaseInfo, null));
unionCase.Constructor = FSharpFunc<object[], object>.ToConverter(FSharpValue.PreComputeUnionConstructor(unionCaseInfo, null));
u.Cases.Add(unionCase);
}
return u;
}
[Test]
public void Serialize()
{
Shape value = Shape.NewRectangle(10.0, 5.0);
Union union = CreateUnion(value.GetType());
int tag = union.TagReader.Invoke(value);
UnionCase caseInfo = union.Cases.Single(c => c.Tag == tag);
object[] fields = caseInfo.FieldReader.Invoke(value);
foreach (object field in fields)
{
Console.WriteLine(field);
}
}
[Test]
public void Deserialize()
{
Union union = CreateUnion(typeof(Shape.Rectangle));
UnionCase caseInfo = union.Cases.Single(c => c.Name == "Rectangle");
Shape.Rectangle value = (Shape.Rectangle)caseInfo.Constructor.Invoke(new object[]
{
10.0, 5.0
});
Console.WriteLine(value.ToString());
Console.WriteLine(value.width);
Console.WriteLine(value.length);
}
[Test]
public void DeserializeBasicUnion_NoMatch()
{
@@ -28,7 +28,6 @@ using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
#if NET20
using Newtonsoft.Json.Utilities.LinqBridge;
#else
@@ -46,9 +45,67 @@ namespace Newtonsoft.Json.Converters
/// </summary>
public class DiscriminatedUnionConverter : JsonConverter
{
#region UnionDefinition
internal class Union
{
public List<UnionCase> Cases;
public Converter<object, int> TagReader { get; set; }
}
internal class UnionCase
{
public int Tag;
public string Name;
public PropertyInfo[] Fields;
public Converter<object, object[]> FieldReader;
public Converter<object[], object> Constructor;
}
#endregion
private const string CasePropertyName = "Case";
private const string FieldsPropertyName = "Fields";
private static readonly ThreadSafeStore<Type, Union> UnionCache = new ThreadSafeStore<Type, Union>(CreateUnion);
private static readonly ThreadSafeStore<Type, Type> UnionTypeLookupCache = new ThreadSafeStore<Type, Type>(CreateUnionTypeLookup);
private static Type CreateUnionTypeLookup(Type t)
{
// this lookup is because cases with fields are derived from union type
// need to get declaring type to avoid duplicate Unions in cache
// hacky but I can't find an API to get the declaring type without GetUnionCases
object[] cases = (object[])FSharpUtils.GetUnionCases(null, t, null);
object caseInfo = cases.First();
Type unionType = (Type)FSharpUtils.GetUnionCaseInfoDeclaringType(caseInfo);
return unionType;
}
private static Union CreateUnion(Type t)
{
Union u = new Union();
u.TagReader = (Converter<object, int>)FSharpUtils.PreComputeUnionTagReader(null, t, null);
u.Cases = new List<UnionCase>();
object[] cases = (object[])FSharpUtils.GetUnionCases(null, t, null);
foreach (object unionCaseInfo in cases)
{
UnionCase unionCase = new UnionCase();
unionCase.Tag = (int)FSharpUtils.GetUnionCaseInfoTag(unionCaseInfo);
unionCase.Name = (string)FSharpUtils.GetUnionCaseInfoName(unionCaseInfo);
unionCase.Fields = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(unionCaseInfo);
unionCase.FieldReader = (Converter<object, object[]>)FSharpUtils.PreComputeUnionReader(null, unionCaseInfo, null);
unionCase.Constructor = (Converter<object[], object>)FSharpUtils.PreComputeUnionConstructor(null, unionCaseInfo, null);
u.Cases.Add(unionCase);
}
return u;
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
@@ -59,21 +116,21 @@ namespace Newtonsoft.Json.Converters
{
DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver;
Type t = value.GetType();
Type unionType = UnionTypeLookupCache.Get(value.GetType());
Union union = UnionCache.Get(unionType);
object result = FSharpUtils.GetUnionFields(null, value, t, null);
object info = FSharpUtils.GetUnionCaseInfo(result);
object fields = FSharpUtils.GetUnionCaseFields(result);
object caseName = FSharpUtils.GetUnionCaseInfoName(info);
object[] fieldsAsArray = fields as object[];
int tag = union.TagReader(value);
UnionCase caseInfo = union.Cases.Single(c => c.Tag == tag);
writer.WriteStartObject();
writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(CasePropertyName) : CasePropertyName);
writer.WriteValue((string)caseName);
if (fieldsAsArray != null && fieldsAsArray.Length > 0)
writer.WriteValue(caseInfo.Name);
if (caseInfo.Fields != null && caseInfo.Fields.Length > 0)
{
object[] fields = caseInfo.FieldReader(value);
writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(FieldsPropertyName) : FieldsPropertyName);
serializer.Serialize(writer, fields);
serializer.Serialize(writer, fields);
}
writer.WriteEndObject();
}
@@ -91,7 +148,7 @@ namespace Newtonsoft.Json.Converters
if (reader.TokenType == JsonToken.Null)
return null;
object matchingCaseInfo = null;
UnionCase caseInfo = null;
string caseName = null;
JArray fields = null;
@@ -105,20 +162,13 @@ namespace Newtonsoft.Json.Converters
{
ReadAndAssert(reader);
IEnumerable cases = (IEnumerable)FSharpUtils.GetUnionCases(null, objectType, null);
Union union = UnionCache.Get(objectType);
caseName = reader.Value.ToString();
foreach (object c in cases)
{
if ((string)FSharpUtils.GetUnionCaseInfoName(c) == caseName)
{
matchingCaseInfo = c;
break;
}
}
caseInfo = union.Cases.SingleOrDefault(c => c.Name == caseName);
if (matchingCaseInfo == null)
if (caseInfo == null)
throw JsonSerializationException.Create(reader, "No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName));
}
else if (string.Equals(propertyName, FieldsPropertyName, StringComparison.OrdinalIgnoreCase))
@@ -137,31 +187,30 @@ namespace Newtonsoft.Json.Converters
ReadAndAssert(reader);
}
if (matchingCaseInfo == null)
if (caseInfo == null)
throw JsonSerializationException.Create(reader, "No '{0}' property with union name found.".FormatWith(CultureInfo.InvariantCulture, CasePropertyName));
PropertyInfo[] fieldProperties = (PropertyInfo[])FSharpUtils.GetUnionCaseInfoFields(matchingCaseInfo);
object[] typedFieldValues = new object[fieldProperties.Length];
if (fieldProperties.Length > 0 && fields == null)
object[] typedFieldValues = new object[caseInfo.Fields.Length];
if (caseInfo.Fields.Length > 0 && fields == null)
throw JsonSerializationException.Create(reader, "No '{0}' property with union fields found.".FormatWith(CultureInfo.InvariantCulture, FieldsPropertyName));
if (fields != null)
{
if (fieldProperties.Length != fields.Count)
if (caseInfo.Fields.Length != fields.Count)
throw JsonSerializationException.Create(reader, "The number of field values does not match the number of properties definied by union '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName));
for (int i = 0; i < fields.Count; i++)
{
JToken t = fields[i];
PropertyInfo fieldProperty = fieldProperties[i];
PropertyInfo fieldProperty = caseInfo.Fields[i];
typedFieldValues[i] = t.ToObject(fieldProperty.PropertyType, serializer);
}
}
return FSharpUtils.MakeUnion(null, matchingCaseInfo, typedFieldValues, null);
return caseInfo.Constructor(typedFieldValues);
}
/// <summary>
+28 -12
View File
@@ -44,12 +44,13 @@ namespace Newtonsoft.Json.Utilities
public static Assembly FSharpCoreAssembly { get; private set; }
public static MethodCall<object, object> IsUnion { get; private set; }
public static MethodCall<object, object> GetUnionFields { get; private set; }
public static MethodCall<object, object> GetUnionCases { get; private set; }
public static MethodCall<object, object> MakeUnion { get; private set; }
public static MethodCall<object, object> PreComputeUnionTagReader { get; private set; }
public static MethodCall<object, object> PreComputeUnionReader { get; private set; }
public static MethodCall<object, object> PreComputeUnionConstructor { get; private set; }
public static Func<object, object> GetUnionCaseInfoDeclaringType { get; private set; }
public static Func<object, object> GetUnionCaseInfoName { get; private set; }
public static Func<object, object> GetUnionCaseInfo { get; private set; }
public static Func<object, object> GetUnionCaseFields { get; private set; }
public static Func<object, object> GetUnionCaseInfoTag { get; private set; }
public static MethodCall<object, object> GetUnionCaseInfoFields { get; private set; }
public const string FSharpSetTypeName = "FSharpSet`1";
@@ -76,18 +77,15 @@ namespace Newtonsoft.Json.Utilities
Type fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue");
MethodInfo getUnionFieldsMethodInfo = fsharpValue.GetMethod("GetUnionFields", BindingFlags.Public | BindingFlags.Static);
GetUnionFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(getUnionFieldsMethodInfo);
GetUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1"));
GetUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2"));
MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static);
MakeUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(makeUnionMethodInfo);
PreComputeUnionTagReader = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionTagReader");
PreComputeUnionReader = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionReader");
PreComputeUnionConstructor = CreateFSharpFuncCall(fsharpValue, "PreComputeUnionConstructor");
Type unionCaseInfo = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo");
GetUnionCaseInfoName = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(unionCaseInfo.GetProperty("Name"));
GetUnionCaseInfoTag = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(unionCaseInfo.GetProperty("Tag"));
GetUnionCaseInfoDeclaringType = JsonTypeReflector.ReflectionDelegateFactory.CreateGet<object>(unionCaseInfo.GetProperty("DeclaringType"));
GetUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(unionCaseInfo.GetMethod("GetFields"));
Type listModule = fsharpCoreAssembly.GetType("Microsoft.FSharp.Collections.ListModule");
@@ -104,6 +102,24 @@ namespace Newtonsoft.Json.Utilities
}
}
private static MethodCall<object, object> CreateFSharpFuncCall(Type type, string methodName)
{
MethodInfo innerMethodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
MethodInfo toConvertMethodInfo = innerMethodInfo.ReturnType.GetMethod("ToConverter", BindingFlags.Public | BindingFlags.Static);
MethodCall<object, object> inner = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(innerMethodInfo);
MethodCall<object, object> toConvert = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall<object>(toConvertMethodInfo);
MethodCall<object, object> methodCall = (target, args) =>
{
object result = inner(target, args);
return toConvert(null, result);
};
return methodCall;
}
public static ObjectConstructor<object> CreateSeq(Type t)
{
MethodInfo seqType = _ofSeq.MakeGenericMethod(t);