1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-07 16:06:15 +00:00

Performance improvements

This commit is contained in:
JKorf 2024-03-21 16:46:17 +01:00
parent db9fba4cf2
commit e86713e949
11 changed files with 80 additions and 24 deletions

View File

@ -46,7 +46,7 @@ namespace CryptoExchange.Net.UnitTests
{
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));

View File

@ -95,7 +95,7 @@ namespace CryptoExchange.Net.Authentication
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"));

View File

@ -248,7 +248,7 @@ namespace CryptoExchange.Net.Authentication
}
/// <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>
@ -270,7 +270,7 @@ namespace CryptoExchange.Net.Authentication
}
/// <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>

View File

@ -323,7 +323,7 @@ namespace CryptoExchange.Net.Clients
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)
@ -341,7 +341,7 @@ namespace CryptoExchange.Net.Clients
// 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

View File

@ -7,7 +7,9 @@ using System;
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
{
@ -222,26 +224,32 @@ namespace CryptoExchange.Net.Converters.JsonNet
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)
@ -284,8 +292,12 @@ namespace CryptoExchange.Net.Converters.JsonNet
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
@ -303,7 +315,13 @@ namespace CryptoExchange.Net.Converters.JsonNet
}
/// <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;

View File

@ -192,9 +192,14 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
[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;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
@ -197,22 +198,28 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
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 if (bufferStream)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
}
else
{
_stream = stream;
// 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)
@ -271,7 +278,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
}
/// <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;

View File

@ -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;
@ -411,6 +413,22 @@ namespace CryptoExchange.Net
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);
}
}
}

View File

@ -3,6 +3,7 @@ using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces
{
@ -83,7 +84,7 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferStream"></param>
bool Read(Stream stream, bool bufferStream);
Task<bool> Read(Stream stream, bool bufferStream);
}
/// <summary>

View File

@ -38,7 +38,7 @@ namespace CryptoExchange.Net.Interfaces
/// <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>

View File

@ -377,7 +377,8 @@ namespace CryptoExchange.Net.Sockets
// 2. Read data into accessor
_accessor.Read(data);
try {
try
{
if (ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData)
{
originalData = _accessor.GetOriginalString();
@ -400,7 +401,7 @@ namespace CryptoExchange.Net.Sockets
lock (_listenersLock)
processors = _listeners.Where(s => s.ListenerIdentifiers.Contains(listenId) && s.CanHandleData).ToList();
if (!processors.Any())
if (processors.Count == 0)
{
if (!ApiClient.UnhandledMessageExpected)
{