Merge pull request #126 from specklesystems/some-optimizations

Some  allocation optimizations
This commit is contained in:
Adam Hathcock
2024-09-26 12:51:13 +01:00
committed by GitHub
10 changed files with 94 additions and 62 deletions
@@ -5,6 +5,7 @@ using GraphQL.Client.Abstractions;
using GraphQL.Client.Abstractions.Websocket;
using Speckle.Newtonsoft.Json;
using Speckle.Newtonsoft.Json.Serialization;
using Speckle.Sdk.Serialisation;
namespace Speckle.Sdk.Api.GraphQL.Serializer;
@@ -73,7 +74,7 @@ internal sealed class NewtonsoftJsonSerializer : IGraphQLWebsocketJsonSerializer
private Task<T> DeserializeFromUtf8Stream<T>(System.IO.Stream stream)
{
using var sr = new StreamReader(stream);
using JsonReader reader = new JsonTextReader(sr);
using JsonReader reader = SpeckleObjectSerializerPool.Instance.GetJsonTextReader(sr);
var serializer = JsonSerializer.Create(JsonSerializerSettings);
return Task.FromResult(serializer.Deserialize<T>(reader));
}
@@ -156,9 +156,7 @@ public partial class Operations
}
// Shoot out the total children count, wasteful
var count = (
await ClosureParser.GetClosuresAsync(objString, localTransport.CancellationToken).ConfigureAwait(false)
).Count;
var count = ClosureParser.GetClosures(objString).Count;
onTotalChildrenCountKnown?.Invoke(count);
@@ -1,4 +1,5 @@
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Serialisation;
namespace Speckle.Sdk.Helpers;
@@ -14,7 +15,7 @@ public sealed class SerializerIdWriter : JsonWriter
{
_jsonWriter = jsonWriter;
_idWriter = new StringWriter();
_jsonIdWriter = new JsonTextWriter(_idWriter);
_jsonIdWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(_idWriter);
}
public (string, JsonWriter) FinishIdWriter()
@@ -17,7 +17,7 @@ public sealed class SpeckleObjectDeserializer
private readonly object _callbackLock = new();
private readonly object?[] _invokeNull = [null];
// id -> Base if already deserialized or id -> Task<object> if was handled by a bg thread
// id -> Base if already deserialized or id -> ValueTask<object> if was handled by a bg thread
private ConcurrentDictionary<string, object?>? _deserializedObjects;
/// <summary>
@@ -46,7 +46,7 @@ public sealed class SpeckleObjectDeserializer
/// <exception cref="ArgumentNullException"><paramref name="rootObjectJson"/> was null</exception>
/// <exception cref="SpeckleDeserializeException"><paramref name="rootObjectJson"/> cannot be deserialised to type <see cref="Base"/></exception>
// /// <exception cref="TransportException"><see cref="ReadTransport"/> did not contain the required json objects (closures)</exception>
public async Task<Base> DeserializeAsync([NotNull] string? rootObjectJson)
public async ValueTask<Base> DeserializeAsync([NotNull] string? rootObjectJson)
{
if (_isBusy)
{
@@ -79,20 +79,19 @@ public sealed class SpeckleObjectDeserializer
}
}
private async Task<object?> DeserializeJsonAsyncInternal(string objectJson)
private async ValueTask<object?> DeserializeJsonAsyncInternal(string objectJson)
{
// Apparently this automatically parses DateTimes in strings if it matches the format:
// JObject doc1 = JObject.Parse(objectJson);
// This is equivalent code that doesn't parse datetimes:
using JsonReader reader = new JsonTextReader(new StringReader(objectJson));
using JsonTextReader reader = SpeckleObjectSerializerPool.Instance.GetJsonTextReader(new StringReader(objectJson));
reader.DateParseHandling = DateParseHandling.None;
object? converted;
try
{
await reader.ReadAsync(CancellationToken).ConfigureAwait(false);
reader.Read();
converted = await ReadObjectAsync(reader, CancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException)
@@ -112,9 +111,9 @@ public sealed class SpeckleObjectDeserializer
}
//this should be buffered
private async Task<List<object?>> ReadArrayAsync(JsonReader reader, CancellationToken ct)
private async ValueTask<List<object?>> ReadArrayAsync(JsonReader reader, CancellationToken ct)
{
await reader.ReadAsync(ct).ConfigureAwait(false);
reader.Read();
List<object?> retList = new();
while (reader.TokenType != JsonToken.EndArray)
{
@@ -127,14 +126,14 @@ public sealed class SpeckleObjectDeserializer
{
retList.Add(convertedValue);
}
await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next
reader.Read(); //goes to next
}
return retList;
}
private async Task<object?> ReadObjectAsync(JsonReader reader, CancellationToken ct)
private async ValueTask<object?> ReadObjectAsync(JsonReader reader, CancellationToken ct)
{
await reader.ReadAsync(ct).ConfigureAwait(false);
reader.Read();
Dictionary<string, object?> dict = new();
while (reader.TokenType != JsonToken.EndObject)
{
@@ -145,8 +144,8 @@ public sealed class SpeckleObjectDeserializer
string propName = (reader.Value?.ToString()).NotNull();
if (propName == "__closure")
{
await reader.ReadAsync(ct).ConfigureAwait(false); //goes to prop value
var closures = await ClosureParser.GetClosuresAsync(reader).ConfigureAwait(false);
reader.Read(); //goes to prop value
var closures = ClosureParser.GetClosures(reader);
foreach (var closure in closures)
{
_ids.Add(closure.Item1);
@@ -159,13 +158,13 @@ public sealed class SpeckleObjectDeserializer
// https://linear.app/speckle/issue/CXPLA-54/when-deserializing-dont-allow-closures-that-arent-downloadable
await TryGetDeserializedAsync(objId).ConfigureAwait(false);
}
await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next
reader.Read(); //goes to next
continue;
}
await reader.ReadAsync(ct).ConfigureAwait(false); //goes prop value
reader.Read(); //goes prop value
object? convertedValue = await ReadPropertyAsync(reader, ct).ConfigureAwait(false);
dict[propName] = convertedValue;
await reader.ReadAsync(ct).ConfigureAwait(false); //goes to next
reader.Read(); //goes to next
}
break;
default:
@@ -188,7 +187,7 @@ public sealed class SpeckleObjectDeserializer
return Dict2Base(dict);
}
private async Task<object?> TryGetDeserializedAsync(string objId)
private async ValueTask<object?> TryGetDeserializedAsync(string objId)
{
object? deserialized = null;
_deserializedObjects.NotNull();
@@ -201,7 +200,23 @@ public sealed class SpeckleObjectDeserializer
{
try
{
deserialized = task.Result;
deserialized = await task.ConfigureAwait(false);
}
catch (AggregateException ex)
{
throw new SpeckleDeserializeException("Failed to deserialize reference object", ex);
}
if (_deserializedObjects.TryAdd(objId, deserialized))
{
_currentCount++;
}
}
if (deserialized is ValueTask<object> valueTask)
{
try
{
deserialized = await valueTask.ConfigureAwait(false);
}
catch (AggregateException ex)
{
@@ -235,7 +250,7 @@ public sealed class SpeckleObjectDeserializer
return deserialized;
}
private async Task<object?> ReadPropertyAsync(JsonReader reader, CancellationToken ct)
private async ValueTask<object?> ReadPropertyAsync(JsonReader reader, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
switch (reader.TokenType)
@@ -251,7 +251,7 @@ public class SpeckleObjectSerializer
}
using var writer = new StringWriter();
using var jsonWriter = new JsonTextWriter(writer);
using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer);
string id = SerializeBaseObject(baseObj, jsonWriter, closure);
var json = writer.ToString();
@@ -275,7 +275,7 @@ public class SpeckleObjectSerializer
ObjectReference objRef = new() { referencedId = id };
using var writer2 = new StringWriter();
using var jsonWriter2 = new JsonTextWriter(writer2);
using var jsonWriter2 = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer2);
SerializeProperty(objRef, jsonWriter2);
var json2 = writer2.ToString();
UpdateParentClosures(id);
@@ -0,0 +1,25 @@
using System.Buffers;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Common;
namespace Speckle.Sdk.Serialisation;
public class SpeckleObjectSerializerPool
{
public static readonly SpeckleObjectSerializerPool Instance = new();
private SpeckleObjectSerializerPool() { }
public JsonTextWriter GetJsonTextWriter(TextWriter writer) => new(writer) { ArrayPool = _charPool };
public JsonTextReader GetJsonTextReader(TextReader reader) => new(reader) { ArrayPool = _charPool };
private readonly SerializerPool<char> _charPool = new(ArrayPool<char>.Create(4096, 4096));
private class SerializerPool<T>(ArrayPool<T> pool) : IArrayPool<T>
{
public T[] Rent(int minimumLength) => pool.Rent(minimumLength);
public void Return(T[]? array) => pool.Return(array.NotNull());
}
}
@@ -5,27 +5,26 @@ namespace Speckle.Sdk.Serialisation.Utilities;
public static class ClosureParser
{
public static async Task<IReadOnlyList<(string, int)>> GetClosuresAsync(
string rootObjectJson,
CancellationToken cancellationToken = default
)
public static IReadOnlyList<(string, int)> GetClosures(string rootObjectJson)
{
try
{
using JsonTextReader reader = new(new StringReader(rootObjectJson));
await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
using JsonTextReader reader = SpeckleObjectSerializerPool.Instance.GetJsonTextReader(
new StringReader(rootObjectJson)
);
reader.Read();
while (reader.TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
{
var closureList = await ReadObjectAsync(reader, cancellationToken).ConfigureAwait(false);
var closureList = ReadObject(reader);
return closureList;
}
default:
await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
await reader.SkipAsync(cancellationToken).ConfigureAwait(false);
reader.Read();
reader.Skip();
break;
}
}
@@ -34,17 +33,12 @@ public static class ClosureParser
return [];
}
public static async Task<IEnumerable<string>> GetChildrenIdsAsync(
string rootObjectJson,
CancellationToken cancellationToken = default
) => (await GetClosuresAsync(rootObjectJson, cancellationToken).ConfigureAwait(false)).Select(x => x.Item1);
public static IEnumerable<string> GetChildrenIds(string rootObjectJson) =>
GetClosures(rootObjectJson).Select(x => x.Item1);
private static async Task<IReadOnlyList<(string, int)>> ReadObjectAsync(
JsonTextReader reader,
CancellationToken cancellationToken
)
private static IReadOnlyList<(string, int)> ReadObject(JsonTextReader reader)
{
await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
reader.Read();
while (reader.TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
@@ -53,46 +47,46 @@ public static class ClosureParser
{
if (reader.Value as string == "__closure")
{
await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to prop vale
var closureList = await ReadClosureEnumerableAsync(reader).ConfigureAwait(false);
reader.Read(); //goes to prop vale
var closureList = ReadClosureEnumerable(reader);
return closureList;
}
await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to prop vale
await reader.SkipAsync(cancellationToken).ConfigureAwait(false);
await reader.ReadAsync(cancellationToken).ConfigureAwait(false); //goes to next
reader.Read(); //goes to prop vale
reader.Skip();
reader.Read(); //goes to next
}
break;
default:
await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
await reader.SkipAsync(cancellationToken).ConfigureAwait(false);
await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
reader.Read();
reader.Skip();
reader.Read();
break;
}
}
return [];
}
public static async Task<IReadOnlyList<(string, int)>> GetClosuresAsync(JsonReader reader)
public static IReadOnlyList<(string, int)> GetClosures(JsonReader reader)
{
if (reader.TokenType != JsonToken.StartObject)
{
return Array.Empty<(string, int)>();
}
var closureList = await ReadClosureEnumerableAsync(reader).ConfigureAwait(false);
var closureList = ReadClosureEnumerable(reader);
closureList.Sort((a, b) => b.Item2.CompareTo(a.Item2));
return closureList;
}
private static async Task<List<(string, int)>> ReadClosureEnumerableAsync(JsonReader reader)
private static List<(string, int)> ReadClosureEnumerable(JsonReader reader)
{
List<(string, int)> closureList = new();
await reader.ReadAsync().ConfigureAwait(false); //startobject
reader.Read(); //startobject
while (reader.TokenType != JsonToken.EndObject)
{
var childId = (reader.Value as string).NotNull(); // propertyName
int childMinDepth = (await reader.ReadAsInt32Async().ConfigureAwait(false)).NotNull(); //propertyValue
await reader.ReadAsync().ConfigureAwait(false);
int childMinDepth = (reader.ReadAsInt32()).NotNull(); //propertyValue
reader.Read();
closureList.Add((childId, childMinDepth));
}
return closureList;
@@ -140,9 +140,7 @@ public sealed class ServerTransport : IServerTransport
api.CancellationToken = CancellationToken;
string? rootObjectJson = await api.DownloadSingleObject(StreamId, id, OnProgressAction).ConfigureAwait(false);
var allIds = (
await ClosureParser.GetChildrenIdsAsync(rootObjectJson.NotNull(), CancellationToken).ConfigureAwait(false)
).ToList();
var allIds = ClosureParser.GetChildrenIds(rootObjectJson.NotNull()).ToList();
var childrenIds = allIds.Where(x => !x.Contains("blob:"));
var blobIds = allIds.Where(x => x.Contains("blob:")).Select(x => x.Remove(0, 5));
@@ -30,7 +30,7 @@ public static class TransportHelpers
targetTransport.SaveObject(id, parent);
var closures = (await ClosureParser.GetChildrenIdsAsync(parent, cancellationToken).ConfigureAwait(false)).ToList();
var closures = ClosureParser.GetChildrenIds(parent).ToList();
onTotalChildrenCountKnown?.Invoke(closures.Count);