Skip to content

Commit

Permalink
Performance improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
JKorf committed Mar 21, 2024
1 parent db9fba4 commit e86713e
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public CallResult<T> Deserialize<T>(string data)
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
var accessor = CreateAccessor();
var valid = accessor.Read(stream, true);
var valid = accessor.Read(stream, true).Result;
if (!valid)
return new CallResult<T>(new ServerError(data));

Expand Down
2 changes: 1 addition & 1 deletion CryptoExchange.Net/Authentication/ApiCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public virtual ApiCredentials Copy()
public ApiCredentials(Stream inputStream, string? identifierKey = null, string? identifierSecret = null)
{
var accessor = new SystemTextJsonStreamMessageAccessor();
if (!accessor.Read(inputStream, false))
if (!accessor.Read(inputStream, false).Result)
throw new ArgumentException("Input stream not valid json data");

var key = accessor.GetValue<string>(MessagePath.Get().Property(identifierKey ?? "apiKey"));
Expand Down
4 changes: 2 additions & 2 deletions CryptoExchange.Net/Authentication/AuthenticationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ protected static byte[] SignMD5Bytes(string data)
}

/// <summary>
/// HMACSHA512 sign the data and return the hash
/// HMACSHA256 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
Expand All @@ -270,7 +270,7 @@ protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null)
}

/// <summary>
/// HMACSHA512 sign the data and return the hash
/// HMACSHA384 sign the data and return the hash
/// </summary>
/// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param>
Expand Down
4 changes: 2 additions & 2 deletions CryptoExchange.Net/Clients/RestApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
if (!response.IsSuccessStatusCode)
{
// Error response
accessor.Read(responseStream, true);
await accessor.Read(responseStream, true).ConfigureAwait(false);

Error error;
if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429)
Expand All @@ -341,7 +341,7 @@ protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
// Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);

var valid = accessor.Read(responseStream, outputOriginalData);
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
if (!valid)
{
// Invalid json
Expand Down
34 changes: 26 additions & 8 deletions CryptoExchange.Net/Converters/JsonNet/JsonNetMessageAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace CryptoExchange.Net.Converters.JsonNet
{
Expand Down Expand Up @@ -222,26 +224,32 @@ public class JsonNetStreamMessageAccessor : JsonNetMessageAccessor, IStreamMessa
public override bool OriginalDataAvailable => _stream?.CanSeek == true;

/// <inheritdoc />
public bool Read(Stream stream, bool bufferStream)
public async Task<bool> Read(Stream stream, bool bufferStream)
{
if (bufferStream && stream is not MemoryStream)
{
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
_stream = new MemoryStream();
stream.CopyTo(_stream);
_stream.Position = 0;
}
else
else if (bufferStream)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
}
else
{
// We don't need to buffer the stream, so don't bother keeping the reference
}

var length = _stream.CanSeek ? _stream.Length : 4096;
using var reader = new StreamReader(_stream, Encoding.UTF8, false, (int)Math.Max(2, length), true);
var length = stream.CanSeek ? stream.Length : 4096;
using var reader = new StreamReader(stream, Encoding.UTF8, false, (int)Math.Max(2, length), true);
using var jsonTextReader = new JsonTextReader(reader);

try
{
_token = JToken.Load(jsonTextReader);
_token = await JToken.LoadAsync(jsonTextReader).ConfigureAwait(false);
IsJson = true;
}
catch (Exception)
Expand Down Expand Up @@ -284,8 +292,12 @@ public class JsonNetByteMessageAccessor : JsonNetMessageAccessor, IByteMessageAc
public bool Read(ReadOnlyMemory<byte> data)
{
_bytes = data;
using var stream = new MemoryStream(data.ToArray());
using var reader = new StreamReader(stream, Encoding.UTF8, false, (int)Math.Max(2, data.Length), true);

// Try getting the underlying byte[] instead of the ToArray to prevent creating a copy
using var stream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var reader = new StreamReader(stream, Encoding.UTF8, false, Math.Max(2, data.Length), true);
using var jsonTextReader = new JsonTextReader(reader);

try
Expand All @@ -303,7 +315,13 @@ public bool Read(ReadOnlyMemory<byte> data)
}

/// <inheritdoc />
public override string GetOriginalString() => Encoding.UTF8.GetString(_bytes.ToArray());
public override string GetOriginalString() =>
// Netstandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
#if NETSTANDARD2_0
Encoding.UTF8.GetString(_bytes.ToArray());
#else
Encoding.UTF8.GetString(_bytes.Span);
#endif

/// <inheritdoc />
public override bool OriginalDataAvailable => true;
Expand Down
9 changes: 7 additions & 2 deletions CryptoExchange.Net/Converters/SystemTextJson/EnumConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,14 @@ private static bool GetValue(Type objectType, List<KeyValuePair<object, string>>
[return: NotNullIfNotNull("enumValue")]
public static string? GetString<T>(T enumValue) => GetString(typeof(T), enumValue);


/// <summary>
/// Get the string value for an enum value using the MapAttribute mapping. When multiple values are mapped for a enum entry the first value will be returned
/// </summary>
/// <param name="objectType"></param>
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
private static string? GetString(Type objectType, object? enumValue)
public static string? GetString(Type objectType, object? enumValue)
{
objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace CryptoExchange.Net.Converters.SystemTextJson
{
Expand Down Expand Up @@ -197,22 +198,28 @@ public class SystemTextJsonStreamMessageAccessor : SystemTextJsonMessageAccessor
public override bool OriginalDataAvailable => _stream?.CanSeek == true;

/// <inheritdoc />
public bool Read(Stream stream, bool bufferStream)
public async Task<bool> Read(Stream stream, bool bufferStream)
{
if (bufferStream && stream is not MemoryStream)
{
// We need to be buffer the stream, and it's not currently a seekable stream, so copy it to a new memory stream
_stream = new MemoryStream();
stream.CopyTo(_stream);
_stream.Position = 0;
}
else
else if (bufferStream)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
}
else
{
// We don't need to buffer the stream, so don't bother keeping the reference
}

try
{
_document = JsonDocument.Parse(_stream);
_document = await JsonDocument.ParseAsync(stream).ConfigureAwait(false);
IsJson = true;
}
catch (Exception)
Expand Down Expand Up @@ -271,7 +278,13 @@ public bool Read(ReadOnlyMemory<byte> data)
}

/// <inheritdoc />
public override string GetOriginalString() => Encoding.UTF8.GetString(_bytes.ToArray());
public override string GetOriginalString() =>
// Netstandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
#if NETSTANDARD2_0
Encoding.UTF8.GetString(_bytes.ToArray());
#else
Encoding.UTF8.GetString(_bytes.Span);
#endif

/// <inheritdoc />
public override bool OriginalDataAvailable => true;
Expand Down
18 changes: 18 additions & 0 deletions CryptoExchange.Net/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Compression;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
Expand Down Expand Up @@ -411,6 +413,22 @@ public static Uri AddQueryParmeter(this Uri uri, string name, string value)

return ub.Uri;
}

/// <summary>
/// Decompress using Gzip
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static ReadOnlyMemory<byte> DecompressGzip(this ReadOnlyMemory<byte> data)
{
using var decompressedStream = new MemoryStream();
using var dataStream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var deflateStream = new GZipStream(new MemoryStream(data.ToArray()), CompressionMode.Decompress);
deflateStream.CopyTo(decompressedStream);
return new ReadOnlyMemory<byte>(decompressedStream.GetBuffer(), 0, (int)decompressedStream.Length);
}
}
}

3 changes: 2 additions & 1 deletion CryptoExchange.Net/Interfaces/IMessageAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace CryptoExchange.Net.Interfaces
{
Expand Down Expand Up @@ -83,7 +84,7 @@ public interface IStreamMessageAccessor : IMessageAccessor
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferStream"></param>
bool Read(Stream stream, bool bufferStream);
Task<bool> Read(Stream stream, bool bufferStream);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion CryptoExchange.Net/Interfaces/IMessageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public interface IMessageProcessor
/// <returns></returns>
Type? GetMessageType(IMessageAccessor messageAccessor);
/// <summary>
/// Deserialize a message int oobject of type
/// Deserialize a message into object of type
/// </summary>
/// <param name="accessor"></param>
/// <param name="type"></param>
Expand Down
5 changes: 3 additions & 2 deletions CryptoExchange.Net/Sockets/SocketConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,8 @@ protected virtual void HandleStreamMessage(WebSocketMessageType type, ReadOnlyMe

// 2. Read data into accessor
_accessor.Read(data);
try {
try
{
if (ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData)
{
originalData = _accessor.GetOriginalString();
Expand All @@ -400,7 +401,7 @@ protected virtual void HandleStreamMessage(WebSocketMessageType type, ReadOnlyMe
lock (_listenersLock)
processors = _listeners.Where(s => s.ListenerIdentifiers.Contains(listenId) && s.CanHandleData).ToList();

if (!processors.Any())
if (processors.Count == 0)
{
if (!ApiClient.UnhandledMessageExpected)
{
Expand Down

0 comments on commit e86713e

Please sign in to comment.