Merge pull request #126 from specklesystems/some-optimizations
Some allocation optimizations
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user