Skip to content

Commit

Permalink
Perf changes (#79)
Browse files Browse the repository at this point in the history
* Use Concurrent Dictionaries and ignore case on them to avoid ToLower

* remove single array allocation

* allocate GetClosures differently

* Use JsonReader for closures

* add comment

* use isdefined

* more readonly with less allocations

* sorts

* fmt

* use element type when making an array
  • Loading branch information
adamhathcock authored Aug 19, 2024
1 parent b14c8db commit 42882cb
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 88 deletions.
46 changes: 8 additions & 38 deletions src/Speckle.Sdk/Serialisation/BaseObjectDeserializerV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public Base Deserialize(string rootObjectJson)
_workerThreads = new DeserializationWorkerThreads(this, WorkerThreadCount);
_workerThreads.Start();

List<(string, int)> closures = GetClosures(rootObjectJson);
closures.Sort((a, b) => b.Item2.CompareTo(a.Item2));
var closures = ClosureParser.GetClosures(rootObjectJson);
int i = 0;
foreach (var closure in closures)
{
Expand Down Expand Up @@ -115,32 +114,6 @@ public Base Deserialize(string rootObjectJson)
}
}

private List<(string, int)> GetClosures(string rootObjectJson)
{
try
{
List<(string, int)> closureList = new();
JObject doc1 = JObject.Parse(rootObjectJson);

if (!doc1.ContainsKey("__closure"))
{
return new List<(string, int)>();
}

foreach (JToken prop in doc1["__closure"].NotNull())
{
string childId = ((JProperty)prop).Name;
int childMinDepth = (int)((JProperty)prop).Value;
closureList.Add((childId, childMinDepth));
}
return closureList;
}
catch (Exception ex) when (!ex.IsFatal())
{
return new List<(string, int)>();
}
}

private object? DeserializeTransportObjectProxy(string? objectJson, long? current, long? total)
{
if (objectJson is null)
Expand Down Expand Up @@ -345,30 +318,26 @@ private Base Dict2Base(Dictionary<string, object?> dictObj)
dictObj.Remove(TYPE_DISCRIMINATOR);
dictObj.Remove("__closure");

Dictionary<string, PropertyInfo> staticProperties = BaseObjectSerializationUtilities.GetTypeProperties(typeName);
List<MethodInfo> onDeserializedCallbacks = BaseObjectSerializationUtilities.GetOnDeserializedCallbacks(typeName);

var staticProperties = BaseObjectSerializationUtilities.GetTypeProperties(typeName);
foreach (var entry in dictObj)
{
string lowerPropertyName = entry.Key.ToLower();
if (staticProperties.TryGetValue(lowerPropertyName, out PropertyInfo? value) && value.CanWrite)
if (staticProperties.TryGetValue(entry.Key, out PropertyInfo? value) && value.CanWrite)
{
PropertyInfo property = staticProperties[lowerPropertyName];
if (entry.Value == null)
{
// Check for JsonProperty(NullValueHandling = NullValueHandling.Ignore) attribute
JsonPropertyAttribute attr = property.GetCustomAttribute<JsonPropertyAttribute>(true);
if (attr != null && attr.NullValueHandling == NullValueHandling.Ignore)
JsonPropertyAttribute attr = value.GetCustomAttribute<JsonPropertyAttribute>(true);
if (attr is { NullValueHandling: NullValueHandling.Ignore })
{
continue;
}
}

Type targetValueType = property.PropertyType;
Type targetValueType = value.PropertyType;
bool conversionOk = ValueConverter.ConvertValue(targetValueType, entry.Value, out object? convertedValue);
if (conversionOk)
{
property.SetValue(baseObj, convertedValue);
value.SetValue(baseObj, convertedValue);
}
else
{
Expand All @@ -390,6 +359,7 @@ private Base Dict2Base(Dictionary<string, object?> dictObj)
bb.filePath = bb.GetLocalDestinationPath(BlobStorageFolder);
}

var onDeserializedCallbacks = BaseObjectSerializationUtilities.GetOnDeserializedCallbacks(typeName);
foreach (MethodInfo onDeserialized in onDeserializedCallbacks)
{
onDeserialized.Invoke(baseObj, new object?[] { null });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Serialization;
using Speckle.Sdk.Host;
Expand All @@ -7,62 +8,47 @@ namespace Speckle.Sdk.Serialisation.SerializationUtilities;
internal static class BaseObjectSerializationUtilities
{
#region Getting Types
private static Dictionary<string, Dictionary<string, PropertyInfo>> s_typeProperties = new();
private static Dictionary<string, List<MethodInfo>> s_onDeserializedCallbacks = new();
private static ConcurrentDictionary<string, IReadOnlyDictionary<string, PropertyInfo>> s_typeProperties = new();
private static ConcurrentDictionary<string, IReadOnlyList<MethodInfo>> s_onDeserializedCallbacks = new();

internal static Dictionary<string, PropertyInfo> GetTypeProperties(string objFullType)
{
lock (s_typeProperties)
{
if (s_typeProperties.TryGetValue(objFullType, out Dictionary<string, PropertyInfo>? value))
{
return value;
}

Dictionary<string, PropertyInfo> ret = new();
Type type = TypeLoader.GetType(objFullType);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo prop in properties)
internal static IReadOnlyDictionary<string, PropertyInfo> GetTypeProperties(string objFullType) =>
s_typeProperties.GetOrAdd(
objFullType,
s =>
{
ret[prop.Name.ToLower()] = prop;
}

value = ret;
s_typeProperties[objFullType] = value;
return value;
}
}

internal static List<MethodInfo> GetOnDeserializedCallbacks(string objFullType)
{
// return new List<MethodInfo>();
lock (s_onDeserializedCallbacks)
{
// System.Runtime.Serialization.Ca
if (s_onDeserializedCallbacks.TryGetValue(objFullType, out List<MethodInfo>? value))
{
return value;
Type type = TypeLoader.GetType(s);
PropertyInfo[] properties = type.GetProperties();
Dictionary<string, PropertyInfo> ret = new(properties.Length, StringComparer.OrdinalIgnoreCase);
foreach (PropertyInfo prop in properties)
{
ret[prop.Name] = prop;
}
return ret;
}
);

List<MethodInfo> ret = new();
Type type = TypeLoader.GetType(objFullType);
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo method in methods)
internal static IReadOnlyList<MethodInfo> GetOnDeserializedCallbacks(string objFullType) =>
s_onDeserializedCallbacks.GetOrAdd(
objFullType,
s =>
{
List<OnDeserializedAttribute> onDeserializedAttributes = method
.GetCustomAttributes<OnDeserializedAttribute>(true)
.ToList();
if (onDeserializedAttributes.Count > 0)
List<MethodInfo>? ret = null;
Type type = TypeLoader.GetType(s);
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo method in methods)
{
ret.Add(method);
if (method.IsDefined(typeof(OnDeserializedAttribute), true))
{
if (ret == null)
{
ret = new List<MethodInfo>();
}
ret.Add(method);
}
}
return (ret as IReadOnlyList<MethodInfo>) ?? Array.Empty<MethodInfo>();
}

value = ret;
s_onDeserializedCallbacks[objFullType] = value;
return value;
}
}
);

/// <summary>
/// Flushes kit's (discriminator, type) cache. Useful if you're dynamically loading more kits at runtime, that provide better coverage of what you're deserialising, and it's now somehow poisoned because the higher level types were not originally available.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Common;

namespace Speckle.Sdk.Serialisation.SerializationUtilities;

public static class ClosureParser
{
public static IReadOnlyList<(string, int)> GetClosures(string rootObjectJson)
{
try
{
using JsonTextReader reader = new(new StringReader(rootObjectJson));
reader.Read();
while (reader.TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
{
var closureList = ReadObject(reader);
if (closureList?.Any() ?? false)
{
closureList.Sort((a, b) => b.Item2.CompareTo(a.Item2));
return closureList;
}
return Array.Empty<(string, int)>();
}
default:
reader.Read();
reader.Skip();
break;
}
}
}
catch (Exception ex) when (!ex.IsFatal()) { }
return Array.Empty<(string, int)>();
}

private static List<(string, int)>? ReadObject(JsonTextReader reader)
{
reader.Read();
while (reader.TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
{
if (reader.Value as string == "__closure")
{
reader.Read(); //goes to prop vale
var closureList = ReadClosureList(reader);
return closureList;
}
reader.Read(); //goes to prop vale
reader.Skip();
reader.Read(); //goes to next
}
break;
default:
reader.Read();
reader.Skip();
reader.Read();
break;
}
}
return null;
}

private static List<(string, int)> ReadClosureList(JsonTextReader reader)
{
List<(string, int)> closureList = new();
reader.Read(); //startobject
while (reader.TokenType != JsonToken.EndObject)
{
var childId = (reader.Value as string).NotNull(); // propertyName
int childMinDepth = reader.ReadAsInt32().NotNull(); //propertyValue
reader.Read();
closureList.Add((childId, childMinDepth));
}

return closureList;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Collections;
using System.Diagnostics.Contracts;
using System.Drawing;
Expand All @@ -9,6 +10,8 @@ namespace Speckle.Sdk.Serialisation.SerializationUtilities;

internal static class ValueConverter
{
private static object[] _singleValue = new object[1];

public static bool ConvertValue(Type type, object? value, out object? convertedValue)
{
// TODO: Document list of supported values in the SDK. (and grow it as needed)
Expand Down Expand Up @@ -165,7 +168,11 @@ public static bool ConvertValue(Type type, object? value, out object? convertedV

var targetType = typeof(List<>).MakeGenericType(type.GenericTypeArguments);
Type listElementType = type.GenericTypeArguments[0];
IList ret = (IList)Activator.CreateInstance(targetType, valueList.Count);

_singleValue[0] = valueList.Count;
//reuse array to avoid params array allocation
IList ret = (IList)Activator.CreateInstance(targetType, _singleValue);

foreach (object inputListElement in valueList)
{
if (!ConvertValue(listElementType, inputListElement, out object? convertedListElement))
Expand Down Expand Up @@ -219,7 +226,7 @@ public static bool ConvertValue(Type type, object? value, out object? convertedV
Type arrayElementType =
type.GetElementType() ?? throw new ArgumentException("IsArray yet not valid element type", nameof(type));

Array ret = (Array)Activator.CreateInstance(type, valueList.Count);
Array ret = Array.CreateInstance(arrayElementType, valueList.Count);
for (int i = 0; i < valueList.Count; i++)
{
object inputListElement = valueList[i];
Expand Down

0 comments on commit 42882cb

Please sign in to comment.