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 stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
var accessor = CreateAccessor(); var accessor = CreateAccessor();
var valid = accessor.Read(stream, true); var valid = accessor.Read(stream, true).Result;
if (!valid) if (!valid)
return new CallResult<T>(new ServerError(data)); 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) public ApiCredentials(Stream inputStream, string? identifierKey = null, string? identifierSecret = null)
{ {
var accessor = new SystemTextJsonStreamMessageAccessor(); var accessor = new SystemTextJsonStreamMessageAccessor();
if (!accessor.Read(inputStream, false)) if (!accessor.Read(inputStream, false).Result)
throw new ArgumentException("Input stream not valid json data"); throw new ArgumentException("Input stream not valid json data");
var key = accessor.GetValue<string>(MessagePath.Get().Property(identifierKey ?? "apiKey")); var key = accessor.GetValue<string>(MessagePath.Get().Property(identifierKey ?? "apiKey"));

View File

@ -248,7 +248,7 @@ namespace CryptoExchange.Net.Authentication
} }
/// <summary> /// <summary>
/// HMACSHA512 sign the data and return the hash /// HMACSHA256 sign the data and return the hash
/// </summary> /// </summary>
/// <param name="data">Data to sign</param> /// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param> /// <param name="outputType">String type</param>
@ -270,7 +270,7 @@ namespace CryptoExchange.Net.Authentication
} }
/// <summary> /// <summary>
/// HMACSHA512 sign the data and return the hash /// HMACSHA384 sign the data and return the hash
/// </summary> /// </summary>
/// <param name="data">Data to sign</param> /// <param name="data">Data to sign</param>
/// <param name="outputType">String type</param> /// <param name="outputType">String type</param>

View File

@ -323,7 +323,7 @@ namespace CryptoExchange.Net.Clients
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
// Error response // Error response
accessor.Read(responseStream, true); await accessor.Read(responseStream, true).ConfigureAwait(false);
Error error; Error error;
if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429) 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 // 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); 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) if (!valid)
{ {
// Invalid json // Invalid json

View File

@ -7,7 +7,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Converters.JsonNet namespace CryptoExchange.Net.Converters.JsonNet
{ {
@ -222,26 +224,32 @@ namespace CryptoExchange.Net.Converters.JsonNet
public override bool OriginalDataAvailable => _stream?.CanSeek == true; public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <inheritdoc /> /// <inheritdoc />
public bool Read(Stream stream, bool bufferStream) public async Task<bool> Read(Stream stream, bool bufferStream)
{ {
if (bufferStream && stream is not MemoryStream) 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 = new MemoryStream();
stream.CopyTo(_stream); stream.CopyTo(_stream);
_stream.Position = 0; _stream.Position = 0;
} }
else else if (bufferStream)
{ {
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream; _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; var length = stream.CanSeek ? stream.Length : 4096;
using var reader = new StreamReader(_stream, Encoding.UTF8, false, (int)Math.Max(2, length), true); using var reader = new StreamReader(stream, Encoding.UTF8, false, (int)Math.Max(2, length), true);
using var jsonTextReader = new JsonTextReader(reader); using var jsonTextReader = new JsonTextReader(reader);
try try
{ {
_token = JToken.Load(jsonTextReader); _token = await JToken.LoadAsync(jsonTextReader).ConfigureAwait(false);
IsJson = true; IsJson = true;
} }
catch (Exception) catch (Exception)
@ -284,8 +292,12 @@ namespace CryptoExchange.Net.Converters.JsonNet
public bool Read(ReadOnlyMemory<byte> data) public bool Read(ReadOnlyMemory<byte> data)
{ {
_bytes = 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); using var jsonTextReader = new JsonTextReader(reader);
try try
@ -303,7 +315,13 @@ namespace CryptoExchange.Net.Converters.JsonNet
} }
/// <inheritdoc /> /// <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 /> /// <inheritdoc />
public override bool OriginalDataAvailable => true; public override bool OriginalDataAvailable => true;

View File

@ -192,9 +192,14 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
[return: NotNullIfNotNull("enumValue")] [return: NotNullIfNotNull("enumValue")]
public static string? GetString<T>(T enumValue) => GetString(typeof(T), 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")] [return: NotNullIfNotNull("enumValue")]
private static string? GetString(Type objectType, object? enumValue) public static string? GetString(Type objectType, object? enumValue)
{ {
objectType = Nullable.GetUnderlyingType(objectType) ?? objectType; objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Converters.SystemTextJson namespace CryptoExchange.Net.Converters.SystemTextJson
{ {
@ -197,22 +198,28 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override bool OriginalDataAvailable => _stream?.CanSeek == true; public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <inheritdoc /> /// <inheritdoc />
public bool Read(Stream stream, bool bufferStream) public async Task<bool> Read(Stream stream, bool bufferStream)
{ {
if (bufferStream && stream is not MemoryStream) 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 = new MemoryStream();
stream.CopyTo(_stream); stream.CopyTo(_stream);
_stream.Position = 0; _stream.Position = 0;
} }
else if (bufferStream)
{
// We need to buffer the stream, and the current stream is seekable, store as is
_stream = stream;
}
else else
{ {
_stream = stream; // We don't need to buffer the stream, so don't bother keeping the reference
} }
try try
{ {
_document = JsonDocument.Parse(_stream); _document = await JsonDocument.ParseAsync(stream).ConfigureAwait(false);
IsJson = true; IsJson = true;
} }
catch (Exception) catch (Exception)
@ -271,7 +278,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
/// <inheritdoc /> /// <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 /> /// <inheritdoc />
public override bool OriginalDataAvailable => true; public override bool OriginalDataAvailable => true;

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Compression;
using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security; using System.Security;
@ -411,6 +413,22 @@ namespace CryptoExchange.Net
return ub.Uri; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
@ -83,7 +84,7 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
/// <param name="stream"></param> /// <param name="stream"></param>
/// <param name="bufferStream"></param> /// <param name="bufferStream"></param>
bool Read(Stream stream, bool bufferStream); Task<bool> Read(Stream stream, bool bufferStream);
} }
/// <summary> /// <summary>

View File

@ -38,7 +38,7 @@ namespace CryptoExchange.Net.Interfaces
/// <returns></returns> /// <returns></returns>
Type? GetMessageType(IMessageAccessor messageAccessor); Type? GetMessageType(IMessageAccessor messageAccessor);
/// <summary> /// <summary>
/// Deserialize a message int oobject of type /// Deserialize a message into object of type
/// </summary> /// </summary>
/// <param name="accessor"></param> /// <param name="accessor"></param>
/// <param name="type"></param> /// <param name="type"></param>

View File

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