From ddeac27f04eefea679b0a4be61a6bc803ffccf62 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 25 Sep 2024 15:52:44 +0100 Subject: [PATCH] Use pool and remove some async --- .../Api/Operations/Operations.Receive.cs | 4 +- .../SpeckleObjectDeserializer.cs | 21 ++++--- .../SpeckleObjectSerializerPool.cs | 25 ++++++++ .../Serialisation/Utilities/ClosureParser.cs | 58 +++++++++---------- src/Speckle.Sdk/Transports/ServerTransport.cs | 4 +- .../Transports/TransportHelpers.cs | 2 +- 6 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 src/Speckle.Sdk/Serialisation/SpeckleObjectSerializerPool.cs diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs index 9c3e7536..0354844e 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Receive.cs @@ -140,9 +140,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); diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs index 84b99404..2183d4d6 100644 --- a/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectDeserializer.cs @@ -78,14 +78,13 @@ public sealed class SpeckleObjectDeserializer // 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) @@ -107,7 +106,7 @@ public sealed class SpeckleObjectDeserializer //this should be buffered private async Task> ReadArrayAsync(JsonReader reader, CancellationToken ct) { - await reader.ReadAsync(ct).ConfigureAwait(false); + reader.Read(); List retList = new(); while (reader.TokenType != JsonToken.EndArray) { @@ -120,14 +119,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 ReadObjectAsync(JsonReader reader, CancellationToken ct) { - await reader.ReadAsync(ct).ConfigureAwait(false); + reader.Read(); Dictionary dict = new(); while (reader.TokenType != JsonToken.EndObject) { @@ -138,8 +137,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); @@ -152,13 +151,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: diff --git a/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializerPool.cs b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializerPool.cs new file mode 100644 index 00000000..90380013 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/SpeckleObjectSerializerPool.cs @@ -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 _charPool = new(ArrayPool.Create(4096, 4096)); + + private class SerializerPool(ArrayPool pool) : IArrayPool + { + public T[] Rent(int minimumLength) => pool.Rent(minimumLength); + + public void Return(T[]? array) => pool.Return(array.NotNull()); + } +} diff --git a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs index 70598054..2730897c 100644 --- a/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs +++ b/src/Speckle.Sdk/Serialisation/Utilities/ClosureParser.cs @@ -5,27 +5,26 @@ namespace Speckle.Sdk.Serialisation.Utilities; public static class ClosureParser { - public static async Task> 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> GetChildrenIdsAsync( - string rootObjectJson, - CancellationToken cancellationToken = default - ) => (await GetClosuresAsync(rootObjectJson, cancellationToken).ConfigureAwait(false)).Select(x => x.Item1); + public static IEnumerable GetChildrenIds(string rootObjectJson) => + GetClosures(rootObjectJson).Select(x => x.Item1); - private static async Task> 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> 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> 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; diff --git a/src/Speckle.Sdk/Transports/ServerTransport.cs b/src/Speckle.Sdk/Transports/ServerTransport.cs index 47045f1f..cdd997ca 100644 --- a/src/Speckle.Sdk/Transports/ServerTransport.cs +++ b/src/Speckle.Sdk/Transports/ServerTransport.cs @@ -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)); diff --git a/src/Speckle.Sdk/Transports/TransportHelpers.cs b/src/Speckle.Sdk/Transports/TransportHelpers.cs index 7111d193..87705d16 100644 --- a/src/Speckle.Sdk/Transports/TransportHelpers.cs +++ b/src/Speckle.Sdk/Transports/TransportHelpers.cs @@ -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);