Permalink
Browse files

-DiscriminatedUnionConverter performance improvements

  • Loading branch information...
1 parent b1184ef commit 95e04ba96e1c1ce40bcaab5c93544f37f9abf2e2 @JamesNK committed Mar 7, 2015
@@ -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>
@@ -32,6 +32,7 @@
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 @@ public static void Main(string[] args)
//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;
@@ -84,6 +88,58 @@ public void SerializeBasicUnion()
}
[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()
{
string json = JsonConvert.SerializeObject(Shape.NewRectangle(10.0, 5.0));
@@ -116,6 +172,82 @@ public void DeserializeUnionWithFields()
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;
@SimonCropp
SimonCropp Mar 19, 2015

this doesn exist in portable. perhaps a custom delegate instead?

+ 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 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 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
{
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 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
if (reader.TokenType == JsonToken.Null)
return null;
- object matchingCaseInfo = null;
+ UnionCase caseInfo = null;
string caseName = null;
JArray fields = null;
@@ -105,20 +162,13 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
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 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
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>
Oops, something went wrong.

0 comments on commit 95e04ba

Please sign in to comment.