Unverified Commit 988d6aef authored by Marc Gravell's avatar Marc Gravell Committed by GitHub

Arenas (#1075)

* remove the entire concept of the completion manager; it just doesn't make sense any more; everything async and external facing should be via the TP

* use "in" with ForAwait to avoid some extra copies

* experimental "backlog queue" approach

* Cleanup and de-dupe timeout exception data

* WriteMessageTakingWriteLockSync should consider backlog

* don't allocate all those strings

* this compiles, but nothing else; not tested yet

* fix the by-ref indexer

* record that CompletedSynchronously has changed - impacts a test

* update benchmarks and rev unofficial

* update to  pipelines 1.1.2 and simplify

* in debug mode, use a small page size in the arena

* rev pipelines to 1.1.3

* Update src/StackExchange.Redis/CommandTrace.cs
Co-Authored-By: 's avatarmgravell <marc.gravell@gmail.com>

* Perf regression (#1076)

* compare baseline to 1.2.6

* remove the entire concept of the completion manager; it just doesn't make sense any more; everything async and external facing should be via the TP

* use "in" with ForAwait to avoid some extra copies

* experimental "backlog queue" approach

* Cleanup and de-dupe timeout exception data

* WriteMessageTakingWriteLockSync should consider backlog

* don't allocate all those strings
parent cfe491ee
...@@ -78,12 +78,13 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -78,12 +78,13 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.MultiBulk: case ResultType.MultiBulk:
var parts = result.GetItems(); var parts = result.GetItems();
CommandTrace[] arr = new CommandTrace[parts.Length]; CommandTrace[] arr = new CommandTrace[parts.Length];
for (int i = 0; i < parts.Length; i++) int i = 0;
foreach(var item in parts)
{ {
var subParts = parts[i].GetItems(); var subParts = item.GetItems();
if (!subParts[0].TryGetInt64(out long uniqueid) || !subParts[1].TryGetInt64(out long time) || !subParts[2].TryGetInt64(out long duration)) if (!subParts[0].TryGetInt64(out long uniqueid) || !subParts[1].TryGetInt64(out long time) || !subParts[2].TryGetInt64(out long duration))
return false; return false;
arr[i] = new CommandTrace(uniqueid, time, duration, subParts[3].GetItemsAsValues()); arr[i++] = new CommandTrace(uniqueid, time, duration, subParts[3].GetItemsAsValues());
} }
SetResult(message, arr); SetResult(message, arr);
return true; return true;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using Pipelines.Sockets.Unofficial.Arenas;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -271,5 +272,13 @@ internal static int VectorSafeIndexOfCRLF(this ReadOnlySpan<byte> span) ...@@ -271,5 +272,13 @@ internal static int VectorSafeIndexOfCRLF(this ReadOnlySpan<byte> span)
return -1; return -1;
} }
#endif #endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T[] ToArray<T>(in this RawResult result, Projection<RawResult, T> selector)
=> result.IsNull ? null : result.GetItems().ToArray(selector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static TTo[] ToArray<TTo, TState>(in this RawResult result, Projection<RawResult, TState, TTo> selector, in TState state)
=> result.IsNull ? null : result.GetItems().ToArray(selector, in state);
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Pipelines.Sockets.Unofficial; using Pipelines.Sockets.Unofficial;
using Pipelines.Sockets.Unofficial.Arenas;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -55,7 +56,7 @@ private static readonly Message ...@@ -55,7 +56,7 @@ private static readonly Message
internal void GetBytes(out long sent, out long received) internal void GetBytes(out long sent, out long received)
{ {
if(_ioPipe is IMeasuredDuplexPipe sc) if (_ioPipe is IMeasuredDuplexPipe sc)
{ {
sent = sc.TotalBytesSent; sent = sc.TotalBytesSent;
received = sc.TotalBytesReceived; received = sc.TotalBytesReceived;
...@@ -268,6 +269,7 @@ public void Dispose() ...@@ -268,6 +269,7 @@ public void Dispose()
RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed); RecordConnectionFailed(ConnectionFailureType.ConnectionDisposed);
} }
OnCloseEcho(); OnCloseEcho();
_arena.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
...@@ -1411,6 +1413,12 @@ private async Task ReadFromPipe() ...@@ -1411,6 +1413,12 @@ private async Task ReadFromPipe()
} }
} }
private static readonly ArenaOptions s_arenaOptions = new ArenaOptions(
#if DEBUG
blockSizeBytes: Unsafe.SizeOf<RawResult>() * 8 // force an absurdly small page size to trigger bugs
#endif
);
private readonly Arena<RawResult> _arena = new Arena<RawResult>(s_arenaOptions);
private int ProcessBuffer(ref ReadOnlySequence<byte> buffer) private int ProcessBuffer(ref ReadOnlySequence<byte> buffer)
{ {
int messageCount = 0; int messageCount = 0;
...@@ -1418,7 +1426,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer) ...@@ -1418,7 +1426,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer)
while (!buffer.IsEmpty) while (!buffer.IsEmpty)
{ {
var reader = new BufferReader(buffer); var reader = new BufferReader(buffer);
var result = TryParseResult(in buffer, ref reader, IncludeDetailInExceptions, BridgeCouldBeNull?.ServerEndPoint); var result = TryParseResult(_arena, in buffer, ref reader, IncludeDetailInExceptions, BridgeCouldBeNull?.ServerEndPoint);
try try
{ {
if (result.HasValue) if (result.HasValue)
...@@ -1436,7 +1444,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer) ...@@ -1436,7 +1444,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer)
} }
finally finally
{ {
result.Recycle(); _arena.Reset();
} }
} }
return messageCount; return messageCount;
...@@ -1466,7 +1474,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer) ...@@ -1466,7 +1474,7 @@ private int ProcessBuffer(ref ReadOnlySequence<byte> buffer)
// } // }
//} //}
private static RawResult ReadArray(in ReadOnlySequence<byte> buffer, ref BufferReader reader, bool includeDetailInExceptions, ServerEndPoint server) private static RawResult ReadArray(Arena<RawResult> arena, in ReadOnlySequence<byte> buffer, ref BufferReader reader, bool includeDetailInExceptions, ServerEndPoint server)
{ {
var itemCount = ReadLineTerminatedString(ResultType.Integer, ref reader); var itemCount = ReadLineTerminatedString(ResultType.Integer, ref reader);
if (itemCount.HasValue) if (itemCount.HasValue)
...@@ -1485,16 +1493,33 @@ private static RawResult ReadArray(in ReadOnlySequence<byte> buffer, ref BufferR ...@@ -1485,16 +1493,33 @@ private static RawResult ReadArray(in ReadOnlySequence<byte> buffer, ref BufferR
return RawResult.EmptyMultiBulk; return RawResult.EmptyMultiBulk;
} }
var oversized = ArrayPool<RawResult>.Shared.Rent(itemCountActual); var oversized = arena.Allocate(itemCountActual);
var result = new RawResult(oversized, itemCountActual); var result = new RawResult(oversized, false);
for (int i = 0; i < itemCountActual; i++)
if (oversized.IsSingleSegment)
{
var span = oversized.FirstSpan;
for(int i = 0; i < span.Length; i++)
{ {
if (!(oversized[i] = TryParseResult(in buffer, ref reader, includeDetailInExceptions, server)).HasValue) if (!(span[i] = TryParseResult(arena, in buffer, ref reader, includeDetailInExceptions, server)).HasValue)
{ {
result.Recycle(i); // passing index here means we don't need to "Array.Clear" before-hand
return RawResult.Nil; return RawResult.Nil;
} }
} }
}
else
{
foreach(var span in oversized.Spans)
{
for (int i = 0; i < span.Length; i++)
{
if (!(span[i] = TryParseResult(arena, in buffer, ref reader, includeDetailInExceptions, server)).HasValue)
{
return RawResult.Nil;
}
}
}
}
return result; return result;
} }
return RawResult.Nil; return RawResult.Nil;
...@@ -1541,7 +1566,7 @@ private static RawResult ReadLineTerminatedString(ResultType type, ref BufferRea ...@@ -1541,7 +1566,7 @@ private static RawResult ReadLineTerminatedString(ResultType type, ref BufferRea
internal void StartReading() => ReadFromPipe().RedisFireAndForget(); internal void StartReading() => ReadFromPipe().RedisFireAndForget();
internal static RawResult TryParseResult(in ReadOnlySequence<byte> buffer, ref BufferReader reader, internal static RawResult TryParseResult(Arena<RawResult> arena, in ReadOnlySequence<byte> buffer, ref BufferReader reader,
bool includeDetilInExceptions, ServerEndPoint server, bool allowInlineProtocol = false) bool includeDetilInExceptions, ServerEndPoint server, bool allowInlineProtocol = false)
{ {
var prefix = reader.PeekByte(); var prefix = reader.PeekByte();
...@@ -1562,15 +1587,15 @@ private static RawResult ReadLineTerminatedString(ResultType type, ref BufferRea ...@@ -1562,15 +1587,15 @@ private static RawResult ReadLineTerminatedString(ResultType type, ref BufferRea
return ReadBulkString(ref reader, includeDetilInExceptions, server); return ReadBulkString(ref reader, includeDetilInExceptions, server);
case '*': // array case '*': // array
reader.Consume(1); reader.Consume(1);
return ReadArray(in buffer, ref reader, includeDetilInExceptions, server); return ReadArray(arena, in buffer, ref reader, includeDetilInExceptions, server);
default: default:
// string s = Format.GetString(buffer); // string s = Format.GetString(buffer);
if (allowInlineProtocol) return ParseInlineProtocol(ReadLineTerminatedString(ResultType.SimpleString, ref reader)); if (allowInlineProtocol) return ParseInlineProtocol(arena, ReadLineTerminatedString(ResultType.SimpleString, ref reader));
throw new InvalidOperationException("Unexpected response prefix: " + (char)prefix); throw new InvalidOperationException("Unexpected response prefix: " + (char)prefix);
} }
} }
private static RawResult ParseInlineProtocol(in RawResult line) private static RawResult ParseInlineProtocol(Arena<RawResult> arena, in RawResult line)
{ {
if (!line.HasValue) return RawResult.Nil; // incomplete line if (!line.HasValue) return RawResult.Nil; // incomplete line
...@@ -1578,13 +1603,14 @@ private static RawResult ParseInlineProtocol(in RawResult line) ...@@ -1578,13 +1603,14 @@ private static RawResult ParseInlineProtocol(in RawResult line)
#pragma warning disable IDE0059 #pragma warning disable IDE0059
foreach (var _ in line.GetInlineTokenizer()) count++; foreach (var _ in line.GetInlineTokenizer()) count++;
#pragma warning restore IDE0059 #pragma warning restore IDE0059
var oversized = ArrayPool<RawResult>.Shared.Rent(count); var block = arena.Allocate(count);
count = 0;
var iter = block.GetEnumerator();
foreach (var token in line.GetInlineTokenizer()) foreach (var token in line.GetInlineTokenizer())
{ { // this assigns *via a reference*, returned via the iterator; just... sweet
oversized[count++] = new RawResult(line.Type, token, false); iter.GetNext() = new RawResult(line.Type, token, false);
} }
return new RawResult(oversized, count); return new RawResult(block, false);
} }
} }
} }
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Pipelines.Sockets.Unofficial.Arenas;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
internal readonly struct RawResult internal readonly struct RawResult
{ {
internal RawResult this[int index] internal ref RawResult this[int index] => ref GetItems()[index];
{
get internal int ItemsCount => (int)_items.Length;
{
if (index >= ItemsCount) throw new IndexOutOfRangeException();
return _itemsOversized[index];
}
}
internal int ItemsCount { get; }
internal ReadOnlySequence<byte> Payload { get; } internal ReadOnlySequence<byte> Payload { get; }
internal static readonly RawResult NullMultiBulk = new RawResult(null, 0); internal static readonly RawResult NullMultiBulk = new RawResult(default(Sequence<RawResult>), isNull: true);
internal static readonly RawResult EmptyMultiBulk = new RawResult(Array.Empty<RawResult>(), 0); internal static readonly RawResult EmptyMultiBulk = new RawResult(default(Sequence<RawResult>), isNull: false);
internal static readonly RawResult Nil = default; internal static readonly RawResult Nil = default;
// note: can't use Memory<RawResult> here - struct recursion breaks runtimr // note: can't use Memory<RawResult> here - struct recursion breaks runtimr
private readonly RawResult[] _itemsOversized; private readonly Sequence _items;
private readonly ResultType _type; private readonly ResultType _type;
private const ResultType NonNullFlag = (ResultType)128; private const ResultType NonNullFlag = (ResultType)128;
...@@ -42,17 +38,14 @@ public RawResult(ResultType resultType, in ReadOnlySequence<byte> payload, bool ...@@ -42,17 +38,14 @@ public RawResult(ResultType resultType, in ReadOnlySequence<byte> payload, bool
if (!isNull) resultType |= NonNullFlag; if (!isNull) resultType |= NonNullFlag;
_type = resultType; _type = resultType;
Payload = payload; Payload = payload;
_itemsOversized = default; _items = default;
ItemsCount = default;
} }
public RawResult(RawResult[] itemsOversized, int itemCount) public RawResult(Sequence<RawResult> items, bool isNull)
{ {
_type = ResultType.MultiBulk; _type = isNull ? ResultType.MultiBulk : (ResultType.MultiBulk | NonNullFlag);
if (itemsOversized != null) _type |= NonNullFlag;
Payload = default; Payload = default;
_itemsOversized = itemsOversized; _items = items.Untyped();
ItemsCount = itemCount;
} }
public bool IsError => Type == ResultType.Error; public bool IsError => Type == ResultType.Error;
...@@ -195,20 +188,6 @@ internal Lease<byte> AsLease() ...@@ -195,20 +188,6 @@ internal Lease<byte> AsLease()
throw new InvalidCastException("Cannot convert to Lease: " + Type); throw new InvalidCastException("Cannot convert to Lease: " + Type);
} }
internal void Recycle(int limit = -1)
{
var arr = _itemsOversized;
if (arr != null)
{
if (limit < 0) limit = ItemsCount;
for (int i = 0; i < limit; i++)
{
arr[i].Recycle();
}
ArrayPool<RawResult>.Shared.Return(arr, clearArray: false);
}
}
internal bool IsEqual(in CommandBytes expected) internal bool IsEqual(in CommandBytes expected)
{ {
if (expected.Length != Payload.Length) return false; if (expected.Length != Payload.Length) return false;
...@@ -284,83 +263,17 @@ internal bool GetBoolean() ...@@ -284,83 +263,17 @@ internal bool GetBoolean()
} }
} }
internal ReadOnlySpan<RawResult> GetItems() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ internal Sequence<RawResult> GetItems() => _items.Cast<RawResult>();
if (Type == ResultType.MultiBulk)
return new ReadOnlySpan<RawResult>(_itemsOversized, 0, ItemsCount);
throw new InvalidOperationException();
}
internal ReadOnlyMemory<RawResult> GetItemsMemory()
{
if (Type == ResultType.MultiBulk)
return new ReadOnlyMemory<RawResult>(_itemsOversized, 0, ItemsCount);
throw new InvalidOperationException();
}
internal RedisKey[] GetItemsAsKeys() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ internal RedisKey[] GetItemsAsKeys() => this.ToArray<RedisKey>((in RawResult x) => x.AsRedisKey());
var items = GetItems();
if (IsNull)
{
return null;
}
else if (items.Length == 0)
{
return Array.Empty<RedisKey>();
}
else
{
var arr = new RedisKey[items.Length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = items[i].AsRedisKey();
}
return arr;
}
}
internal RedisValue[] GetItemsAsValues() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ internal RedisValue[] GetItemsAsValues() => this.ToArray<RedisValue>((in RawResult x) => x.AsRedisValue());
var items = GetItems();
if (IsNull) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ internal string[] GetItemsAsStrings() => this.ToArray<string>((in RawResult x) => (string)x.AsRedisValue());
return null;
}
else if (items.Length == 0)
{
return RedisValue.EmptyArray;
}
else
{
var arr = new RedisValue[items.Length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = items[i].AsRedisValue();
}
return arr;
}
}
internal string[] GetItemsAsStrings()
{
var items = GetItems();
if (IsNull)
{
return null;
}
else if (items.Length == 0)
{
return Array.Empty<string>();
}
else
{
var arr = new string[items.Length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = (string)(items[i].AsRedisValue());
}
return arr;
}
}
internal GeoPosition? GetItemsAsGeoPosition() internal GeoPosition? GetItemsAsGeoPosition()
{ {
...@@ -370,43 +283,36 @@ internal string[] GetItemsAsStrings() ...@@ -370,43 +283,36 @@ internal string[] GetItemsAsStrings()
return null; return null;
} }
var coords = items[0].GetItems(); ref RawResult root = ref items[0];
if (items[0].IsNull) if (root.IsNull)
{ {
return null; return null;
} }
return new GeoPosition((double)coords[0].AsRedisValue(), (double)coords[1].AsRedisValue()); return AsGeoPosition(root.GetItems());
} }
internal GeoPosition?[] GetItemsAsGeoPositionArray() static GeoPosition AsGeoPosition(Sequence<RawResult> coords)
{
var items = GetItems();
if (IsNull)
{ {
return null; double longitude, latitude;
} if (coords.IsSingleSegment)
else if (items.Length == 0)
{ {
return Array.Empty<GeoPosition?>(); var span = coords.FirstSpan;
longitude = (double)span[0].AsRedisValue();
latitude = (double)span[1].AsRedisValue();
} }
else else
{ {
var arr = new GeoPosition?[items.Length]; var iter = coords.GetEnumerator();
for (int i = 0; i < arr.Length; i++) longitude = (double)iter.GetNext().AsRedisValue();
{ latitude = (double)iter.GetNext().AsRedisValue();
var item = items[i].GetItems();
if (items[i].IsNull)
{
arr[i] = null;
}
else
{
arr[i] = new GeoPosition((double)item[0].AsRedisValue(), (double)item[1].AsRedisValue());
}
}
return arr;
} }
return new GeoPosition(longitude, latitude);
} }
internal GeoPosition?[] GetItemsAsGeoPositionArray()
=> this.ToArray<GeoPosition?>((in RawResult item) => item.IsNull ? (GeoPosition?)null : AsGeoPosition(item.GetItems()));
internal unsafe string GetString() internal unsafe string GetString()
{ {
if (IsNull) return null; if (IsNull) return null;
......
...@@ -3494,13 +3494,16 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -3494,13 +3494,16 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
{ {
case ResultType.MultiBulk: case ResultType.MultiBulk:
var arr = result.GetItems(); var arr = result.GetItems();
long i64; if (arr.Length == 2)
if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64))
{ {
var sscanResult = new ScanIterator<T>.ScanResult(i64, Parse(arr[1])); ref RawResult inner = ref arr[1];
if (inner.Type == ResultType.MultiBulk && arr[0].TryGetInt64(out var i64))
{
var sscanResult = new ScanIterator<T>.ScanResult(i64, Parse(inner));
SetResult(message, sscanResult); SetResult(message, sscanResult);
return true; return true;
} }
}
break; break;
} }
return false; return false;
......
...@@ -60,11 +60,13 @@ internal static RedisResult TryCreate(PhysicalConnection connection, in RawResul ...@@ -60,11 +60,13 @@ internal static RedisResult TryCreate(PhysicalConnection connection, in RawResul
var items = result.GetItems(); var items = result.GetItems();
if (items.Length == 0) return EmptyArray; if (items.Length == 0) return EmptyArray;
var arr = new RedisResult[items.Length]; var arr = new RedisResult[items.Length];
for (int i = 0; i < arr.Length; i++) int i = 0;
var iter = items.GetEnumerator();
while (iter.MoveNext())
{ {
var next = TryCreate(connection, items[i]); var next = TryCreate(connection, in iter.CurrentReference);
if (next == null) return null; // means we didn't understand if (next == null) return null; // means we didn't understand
arr[i] = next; arr[i++] = next;
} }
return new ArrayRedisResult(arr); return new ArrayRedisResult(arr);
case ResultType.Error: case ResultType.Error:
......
...@@ -755,9 +755,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -755,9 +755,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.MultiBulk: case ResultType.MultiBulk:
var arr = result.GetItems(); var arr = result.GetItems();
long i64; long i64;
if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64)) RawResult inner;
if (arr.Length == 2 && (inner = arr[1]).Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64))
{ {
var keysResult = new ScanResult(i64, arr[1].GetItemsAsKeys()); var keysResult = new ScanResult(i64, inner.GetItemsAsKeys());
SetResult(message, keysResult); SetResult(message, keysResult);
return true; return true;
} }
......
...@@ -503,11 +503,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -503,11 +503,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
{ {
connection.Trace("Server committed; processing nested replies"); connection.Trace("Server committed; processing nested replies");
connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog($"processing {arr.Length} wrapped messages"); connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog($"processing {arr.Length} wrapped messages");
for (int i = 0; i < arr.Length; i++)
int i = 0;
var iter = arr.GetEnumerator();
while(iter.MoveNext())
{ {
var inner = wrapped[i].Wrapped; var inner = wrapped[i++].Wrapped;
connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog($"> got {arr[i]} for {inner.CommandAndKey}"); connection?.BridgeCouldBeNull?.Multiplexer?.OnTransactionLog($"> got {iter.Current} for {inner.CommandAndKey}");
if (inner.ComputeResult(connection, arr[i])) if (inner.ComputeResult(connection, iter.CurrentReference))
{ {
inner.Complete(); inner.Complete();
} }
......
...@@ -5,8 +5,9 @@ ...@@ -5,8 +5,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Pipelines.Sockets.Unofficial.Arenas;
namespace StackExchange.Redis namespace StackExchange.Redis
{ {
...@@ -563,7 +564,7 @@ public bool TryParse(in RawResult result, out T[] pairs) ...@@ -563,7 +564,7 @@ public bool TryParse(in RawResult result, out T[] pairs)
} }
else else
{ {
int count = arr.Length / 2; int count = (int)arr.Length / 2;
if (count == 0) if (count == 0)
{ {
pairs = Array.Empty<T>(); pairs = Array.Empty<T>();
...@@ -571,10 +572,22 @@ public bool TryParse(in RawResult result, out T[] pairs) ...@@ -571,10 +572,22 @@ public bool TryParse(in RawResult result, out T[] pairs)
else else
{ {
pairs = new T[count]; pairs = new T[count];
if (arr.IsSingleSegment)
{
var span = arr.FirstSpan;
int offset = 0; int offset = 0;
for (int i = 0; i < pairs.Length; i++) for (int i = 0; i < pairs.Length; i++)
{ {
pairs[i] = Parse(arr[offset++], arr[offset++]); pairs[i] = Parse(span[offset++], span[offset++]);
}
}
else
{
var iter = arr.GetEnumerator(); // simplest way of getting successive values
for (int i = 0; i < pairs.Length; i++)
{
pairs[i] = Parse(iter.GetNext(), iter.GetNext());
}
} }
} }
} }
...@@ -703,13 +716,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -703,13 +716,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.MultiBulk: case ResultType.MultiBulk:
if (message?.Command == RedisCommand.CONFIG) if (message?.Command == RedisCommand.CONFIG)
{ {
var arr = result.GetItems(); var iter = result.GetItems().GetEnumerator();
int count = arr.Length / 2; while(iter.MoveNext())
for (int i = 0; i < count; i++)
{ {
var key = arr[i * 2]; ref RawResult key = ref iter.CurrentReference;
if (key.IsEqual(CommonReplies.timeout) && arr[(i * 2) + 1].TryGetInt64(out long i64)) if (!iter.MoveNext()) break;
ref RawResult val = ref iter.CurrentReference;
if (key.IsEqual(CommonReplies.timeout) && val.TryGetInt64(out long i64))
{ {
// note the configuration is in seconds // note the configuration is in seconds
int timeoutSeconds = checked((int)i64), targetSeconds; int timeoutSeconds = checked((int)i64), targetSeconds;
...@@ -727,7 +741,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -727,7 +741,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
server.WriteEverySeconds = targetSeconds; server.WriteEverySeconds = targetSeconds;
} }
} }
else if (key.IsEqual(CommonReplies.databases) && arr[(i * 2) + 1].TryGetInt64(out i64)) else if (key.IsEqual(CommonReplies.databases) && val.TryGetInt64(out i64))
{ {
int dbCount = checked((int)i64); int dbCount = checked((int)i64);
server.Multiplexer.Trace("Auto-configured databases: " + dbCount); server.Multiplexer.Trace("Auto-configured databases: " + dbCount);
...@@ -735,7 +749,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -735,7 +749,6 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
else if (key.IsEqual(CommonReplies.slave_read_only)) else if (key.IsEqual(CommonReplies.slave_read_only))
{ {
var val = arr[(i * 2) + 1];
if (val.IsEqual(CommonReplies.yes)) if (val.IsEqual(CommonReplies.yes))
{ {
server.SlaveReadOnly = true; server.SlaveReadOnly = true;
...@@ -892,7 +905,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -892,7 +905,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (arr.Length) switch (arr.Length)
{ {
case 1: case 1:
if (arr[0].TryGetInt64(out unixTime)) if (arr.FirstSpan[0].TryGetInt64(out unixTime))
{ {
var time = RedisBase.UnixEpoch.AddSeconds(unixTime); var time = RedisBase.UnixEpoch.AddSeconds(unixTime);
SetResult(message, time); SetResult(message, time);
...@@ -1101,26 +1114,25 @@ public RedisChannelArrayProcessor(RedisChannel.PatternMode mode) ...@@ -1101,26 +1114,25 @@ public RedisChannelArrayProcessor(RedisChannel.PatternMode mode)
this.mode = mode; this.mode = mode;
} }
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) readonly struct ChannelState // I would use a value-tuple here, but that is binding hell
{ {
switch (result.Type) public readonly byte[] Prefix;
public readonly RedisChannel.PatternMode Mode;
public ChannelState(byte[] prefix, RedisChannel.PatternMode mode)
{ {
case ResultType.MultiBulk: Prefix = prefix;
var arr = result.GetItems(); Mode = mode;
RedisChannel[] final;
if (arr.Length == 0)
{
final = Array.Empty<RedisChannel>();
} }
else }
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
{ {
final = new RedisChannel[arr.Length]; switch (result.Type)
byte[] channelPrefix = connection.ChannelPrefix;
for (int i = 0; i < final.Length; i++)
{ {
final[i] = arr[i].AsRedisChannel(channelPrefix, mode); case ResultType.MultiBulk:
} var final = result.ToArray(
} (in RawResult item, in ChannelState state) => item.AsRedisChannel(state.Prefix, state.Mode),
new ChannelState(connection.ChannelPrefix, mode));
SetResult(message, final); SetResult(message, final);
return true; return true;
} }
...@@ -1274,29 +1286,15 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1274,29 +1286,15 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
switch (result.Type) switch (result.Type)
{ {
case ResultType.MultiBulk: case ResultType.MultiBulk:
var arr = result.GetItems(); var typed = result.ToArray(
(in RawResult item, in GeoRadiusOptions options) => Parse(item, options), this.options);
GeoRadiusResult[] typed;
if (result.IsNull)
{
typed = null;
}
else
{
var options = this.options;
typed = new GeoRadiusResult[arr.Length];
for (int i = 0; i < arr.Length; i++)
{
typed[i] = Parse(options, arr[i]);
}
}
SetResult(message, typed); SetResult(message, typed);
return true; return true;
} }
return false; return false;
} }
private static GeoRadiusResult Parse(GeoRadiusOptions options, in RawResult item) private static GeoRadiusResult Parse(in RawResult item, GeoRadiusOptions options)
{ {
if (options == GeoRadiusOptions.None) if (options == GeoRadiusOptions.None)
{ {
...@@ -1304,11 +1302,10 @@ private static GeoRadiusResult Parse(GeoRadiusOptions options, in RawResult item ...@@ -1304,11 +1302,10 @@ private static GeoRadiusResult Parse(GeoRadiusOptions options, in RawResult item
return new GeoRadiusResult(item.AsRedisValue(), null, null, null); return new GeoRadiusResult(item.AsRedisValue(), null, null, null);
} }
// If WITHCOORD, WITHDIST or WITHHASH options are specified, the command returns an array of arrays, where each sub-array represents a single item. // If WITHCOORD, WITHDIST or WITHHASH options are specified, the command returns an array of arrays, where each sub-array represents a single item.
var arr = item.GetItems(); var iter = item.GetItems().GetEnumerator();
int index = 0;
// the first item in the sub-array is always the name of the returned item. // the first item in the sub-array is always the name of the returned item.
var member = arr[index++].AsRedisValue(); var member = iter.GetNext().AsRedisValue();
/* The other information is returned in the following order as successive elements of the sub-array. /* The other information is returned in the following order as successive elements of the sub-array.
The distance from the center as a floating point number, in the same unit specified in the radius. The distance from the center as a floating point number, in the same unit specified in the radius.
...@@ -1318,11 +1315,11 @@ private static GeoRadiusResult Parse(GeoRadiusOptions options, in RawResult item ...@@ -1318,11 +1315,11 @@ private static GeoRadiusResult Parse(GeoRadiusOptions options, in RawResult item
double? distance = null; double? distance = null;
GeoPosition? position = null; GeoPosition? position = null;
long? hash = null; long? hash = null;
if ((options & GeoRadiusOptions.WithDistance) != 0) { distance = (double?)arr[index++].AsRedisValue(); } if ((options & GeoRadiusOptions.WithDistance) != 0) { distance = (double?)iter.GetNext().AsRedisValue(); }
if ((options & GeoRadiusOptions.WithGeoHash) != 0) { hash = (long?)arr[index++].AsRedisValue(); } if ((options & GeoRadiusOptions.WithGeoHash) != 0) { hash = (long?)iter.GetNext().AsRedisValue(); }
if ((options & GeoRadiusOptions.WithCoordinates) != 0) if ((options & GeoRadiusOptions.WithCoordinates) != 0)
{ {
var coords = arr[index++].GetItems(); var coords = iter.GetNext().GetItems();
double longitude = (double)coords[0].AsRedisValue(), latitude = (double)coords[1].AsRedisValue(); double longitude = (double)coords[0].AsRedisValue(), latitude = (double)coords[1].AsRedisValue();
position = new GeoPosition(longitude, latitude); position = new GeoPosition(longitude, latitude);
} }
...@@ -1495,33 +1492,21 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1495,33 +1492,21 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
var arr = result.GetItems(); var streams = result.GetItems().ToArray((in RawResult item, in MultiStreamProcessor obj) =>
var streams = ConvertAll(arr, item =>
{ {
var details = item.GetItems(); var details = item.GetItems().GetEnumerator();
// details[0] = Name of the Stream // details[0] = Name of the Stream
// details[1] = Multibulk Array of Stream Entries // details[1] = Multibulk Array of Stream Entries
return new RedisStream(key: details.GetNext().AsRedisKey(),
return new RedisStream(key: details[0].AsRedisKey(), entries: obj.ParseRedisStreamEntries(details.GetNext()));
entries: ParseRedisStreamEntries(details[1])); }, this);
});
SetResult(message, streams); SetResult(message, streams);
return true; return true;
} }
} }
private static T[] ConvertAll<T>(ReadOnlySpan<RawResult> items, Func<RawResult, T> converter)
{
if (items.Length == 0) return Array.Empty<T>();
T[] arr = new T[items.Length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = converter(items[i]);
}
return arr;
}
internal sealed class StreamConsumerInfoProcessor : InterleavedStreamInfoProcessorBase<StreamConsumerInfo> internal sealed class StreamConsumerInfoProcessor : InterleavedStreamInfoProcessorBase<StreamConsumerInfo>
{ {
protected override StreamConsumerInfo ParseItem(in RawResult result) protected override StreamConsumerInfo ParseItem(in RawResult result)
...@@ -1592,7 +1577,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1592,7 +1577,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
} }
var arr = result.GetItems(); var arr = result.GetItems();
var parsedItems = ConvertAll(arr, item => ParseItem(item)); var parsedItems = arr.ToArray((in RawResult item, in InterleavedStreamInfoProcessorBase<T> obj) => obj.ParseItem(item), this);
SetResult(message, parsedItems); SetResult(message, parsedItems);
return true; return true;
...@@ -1634,9 +1619,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1634,9 +1619,10 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
long length = -1, radixTreeKeys = -1, radixTreeNodes = -1, groups = -1; long length = -1, radixTreeKeys = -1, radixTreeNodes = -1, groups = -1;
var lastGeneratedId = Redis.RedisValue.Null; var lastGeneratedId = Redis.RedisValue.Null;
StreamEntry firstEntry = StreamEntry.Null, lastEntry = StreamEntry.Null; StreamEntry firstEntry = StreamEntry.Null, lastEntry = StreamEntry.Null;
for(int index = 0, i = 0; i < max; i++) var iter = arr.GetEnumerator();
for(int i = 0; i < max; i++)
{ {
RawResult key = arr[index++], value = arr[index++]; ref RawResult key = ref iter.GetNext(), value = ref iter.GetNext();
if (key.Payload.Length > CommandBytes.MaxLength) continue; if (key.Payload.Length > CommandBytes.MaxLength) continue;
var keyBytes = new CommandBytes(key.Payload); var keyBytes = new CommandBytes(key.Payload);
...@@ -1714,15 +1700,16 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1714,15 +1700,16 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
// If there are no consumers as of yet for the given group, the last // If there are no consumers as of yet for the given group, the last
// item in the response array will be null. // item in the response array will be null.
if (!arr[3].IsNull) ref RawResult third = ref arr[3];
if (!third.IsNull)
{ {
consumers = ConvertAll(arr[3].GetItems(), item => consumers = third.ToArray((in RawResult item) =>
{ {
var details = item.GetItems(); var details = item.GetItems().GetEnumerator();
return new StreamConsumer( return new StreamConsumer(
name: details[0].AsRedisValue(), name: details.GetNext().AsRedisValue(),
pendingMessageCount: (int)details[1].AsRedisValue()); pendingMessageCount: (int)details.GetNext().AsRedisValue());
}); });
} }
...@@ -1747,16 +1734,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -1747,16 +1734,14 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false; return false;
} }
var arr = result.GetItems(); var messageInfoArray = result.GetItems().ToArray((in RawResult item) =>
var messageInfoArray = ConvertAll(arr, item =>
{ {
var details = item.GetItems(); var details = item.GetItems().GetEnumerator();
return new StreamPendingMessageInfo(messageId: details[0].AsRedisValue(), return new StreamPendingMessageInfo(messageId: details.GetNext().AsRedisValue(),
consumerName: details[1].AsRedisValue(), consumerName: details.GetNext().AsRedisValue(),
idleTimeInMs: (long)details[2].AsRedisValue(), idleTimeInMs: (long)details.GetNext().AsRedisValue(),
deliveryCount: (int)details[3].AsRedisValue()); deliveryCount: (int)details.GetNext().AsRedisValue());
}); });
SetResult(message, messageInfoArray); SetResult(message, messageInfoArray);
...@@ -1789,9 +1774,8 @@ protected StreamEntry[] ParseRedisStreamEntries(in RawResult result) ...@@ -1789,9 +1774,8 @@ protected StreamEntry[] ParseRedisStreamEntries(in RawResult result)
return null; return null;
} }
var arr = result.GetItems(); return result.GetItems().ToArray(
(in RawResult item, in StreamProcessorBase<T> obj) => obj.ParseRedisStreamEntry(item), this);
return ConvertAll(arr, item => ParseRedisStreamEntry(item));
} }
protected NameValueEntry[] ParseStreamEntryValues(in RawResult result) protected NameValueEntry[] ParseStreamEntryValues(in RawResult result)
...@@ -1819,17 +1803,17 @@ protected NameValueEntry[] ParseStreamEntryValues(in RawResult result) ...@@ -1819,17 +1803,17 @@ protected NameValueEntry[] ParseStreamEntryValues(in RawResult result)
var arr = result.GetItems(); var arr = result.GetItems();
// Calculate how many name/value pairs are in the stream entry. // Calculate how many name/value pairs are in the stream entry.
int count = arr.Length / 2; int count = (int)arr.Length / 2;
if (count == 0) return Array.Empty<NameValueEntry>(); if (count == 0) return Array.Empty<NameValueEntry>();
var pairs = new NameValueEntry[count]; var pairs = new NameValueEntry[count];
int offset = 0;
var iter = arr.GetEnumerator();
for (int i = 0; i < pairs.Length; i++) for (int i = 0; i < pairs.Length; i++)
{ {
pairs[i] = new NameValueEntry(arr[offset++].AsRedisValue(), pairs[i] = new NameValueEntry(iter.GetNext().AsRedisValue(),
arr[offset++].AsRedisValue()); iter.GetNext().AsRedisValue());
} }
return pairs; return pairs;
...@@ -2008,14 +1992,12 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes ...@@ -2008,14 +1992,12 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
case ResultType.MultiBulk: case ResultType.MultiBulk:
var arrayOfArrays = result.GetItems(); var arrayOfArrays = result.GetItems();
var returnArray = new KeyValuePair<string, string>[arrayOfArrays.Length][]; var returnArray = result.ToArray<KeyValuePair<string, string>[], StringPairInterleavedProcessor>(
(in RawResult rawInnerArray, in StringPairInterleavedProcessor proc) =>
for (int i = 0; i < arrayOfArrays.Length; i++)
{ {
var rawInnerArray = arrayOfArrays[i]; proc.TryParse(rawInnerArray, out KeyValuePair<string, string>[] kvpArray);
innerProcessor.TryParse(rawInnerArray, out KeyValuePair<string, string>[] kvpArray); return kvpArray;
returnArray[i] = kvpArray; }, innerProcessor);
}
SetResult(message, returnArray); SetResult(message, returnArray);
return true; return true;
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Pipelines.Sockets.Unofficial" Version="1.0.26" /> <PackageReference Include="Pipelines.Sockets.Unofficial" Version="1.1.3" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.5.0" /> <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.5.0" />
<PackageReference Include="System.IO.Pipelines" Version="4.5.1" /> <PackageReference Include="System.IO.Pipelines" Version="4.5.1" />
<PackageReference Include="System.Threading.Channels" Version="4.5.0" /> <PackageReference Include="System.Threading.Channels" Version="4.5.0" />
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.11.1" /> <PackageReference Include="BenchmarkDotNet" Version="0.11.4" />
<ProjectReference Include="..\..\src\StackExchange.Redis\StackExchange.Redis.csproj" /> <ProjectReference Include="..\..\src\StackExchange.Redis\StackExchange.Redis.csproj" />
</ItemGroup> </ItemGroup>
......
...@@ -26,7 +26,7 @@ protected virtual Job Configure(Job j) ...@@ -26,7 +26,7 @@ protected virtual Job Configure(Job j)
public CustomConfig() public CustomConfig()
{ {
Add(new MemoryDiagnoser()); Add(MemoryDiagnoser.Default);
Add(StatisticColumn.OperationsPerSecond); Add(StatisticColumn.OperationsPerSecond);
Add(JitOptimizationsValidator.FailOnError); Add(JitOptimizationsValidator.FailOnError);
...@@ -64,11 +64,18 @@ public void Setup() ...@@ -64,11 +64,18 @@ public void Setup()
connection = ConnectionMultiplexer.Connect(options); connection = ConnectionMultiplexer.Connect(options);
db = connection.GetDatabase(3); db = connection.GetDatabase(3);
db.KeyDelete(GeoKey, CommandFlags.FireAndForget); db.KeyDelete(GeoKey);
db.GeoAdd(GeoKey, 13.361389, 38.115556, "Palermo "); db.GeoAdd(GeoKey, 13.361389, 38.115556, "Palermo ");
db.GeoAdd(GeoKey, 15.087269, 37.502669, "Catania"); db.GeoAdd(GeoKey, 15.087269, 37.502669, "Catania");
db.KeyDelete(HashKey);
for (int i = 0; i < 1000; i++)
{
db.HashSet(HashKey, i, i);
}
} }
private static readonly RedisKey GeoKey = "GeoTest", IncrByKey = "counter", StringKey = "string";
private static readonly RedisKey GeoKey = "GeoTest", IncrByKey = "counter", StringKey = "string", HashKey = "hash";
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
mgr?.Dispose(); mgr?.Dispose();
...@@ -78,16 +85,13 @@ void IDisposable.Dispose() ...@@ -78,16 +85,13 @@ void IDisposable.Dispose()
connection = null; connection = null;
} }
private const int COUNT = 500; private const int COUNT = 50;
/// <summary> /// <summary>
/// Run INCRBY lots of times /// Run INCRBY lots of times
/// </summary> /// </summary>
#if TEST_BASELINE // [Benchmark(Description = "INCRBY/s", OperationsPerInvoke = COUNT)]
// [Benchmark(Description = "INCRBY:v1/s", OperationsPerInvoke = COUNT)]
#else
// [Benchmark(Description = "INCRBY:v2/s", OperationsPerInvoke = COUNT)]
#endif
public int ExecuteIncrBy() public int ExecuteIncrBy()
{ {
var rand = new Random(12345); var rand = new Random(12345);
...@@ -108,11 +112,7 @@ public int ExecuteIncrBy() ...@@ -108,11 +112,7 @@ public int ExecuteIncrBy()
/// <summary> /// <summary>
/// Run INCRBY lots of times /// Run INCRBY lots of times
/// </summary> /// </summary>
#if TEST_BASELINE // [Benchmark(Description = "INCRBY/a", OperationsPerInvoke = COUNT)]
// [Benchmark(Description = "INCRBY:v1/a", OperationsPerInvoke = COUNT)]
#else
// [Benchmark(Description = "INCRBY:v2/a", OperationsPerInvoke = COUNT)]
#endif
public async Task<int> ExecuteIncrByAsync() public async Task<int> ExecuteIncrByAsync()
{ {
var rand = new Random(12345); var rand = new Random(12345);
...@@ -133,11 +133,7 @@ public async Task<int> ExecuteIncrByAsync() ...@@ -133,11 +133,7 @@ public async Task<int> ExecuteIncrByAsync()
/// <summary> /// <summary>
/// Run GEORADIUS lots of times /// Run GEORADIUS lots of times
/// </summary> /// </summary>
#if TEST_BASELINE // [Benchmark(Description = "GEORADIUS/s", OperationsPerInvoke = COUNT)]
// [Benchmark(Description = "GEORADIUS:v1/s", OperationsPerInvoke = COUNT)]
#else
// [Benchmark(Description = "GEORADIUS:v2/s", OperationsPerInvoke = COUNT)]
#endif
public int ExecuteGeoRadius() public int ExecuteGeoRadius()
{ {
int total = 0; int total = 0;
...@@ -153,11 +149,7 @@ public int ExecuteGeoRadius() ...@@ -153,11 +149,7 @@ public int ExecuteGeoRadius()
/// <summary> /// <summary>
/// Run GEORADIUS lots of times /// Run GEORADIUS lots of times
/// </summary> /// </summary>
#if TEST_BASELINE // [Benchmark(Description = "GEORADIUS/a", OperationsPerInvoke = COUNT)]
// [Benchmark(Description = "GEORADIUS:v1/a", OperationsPerInvoke = COUNT)]
#else
// [Benchmark(Description = "GEORADIUS:v2/a", OperationsPerInvoke = COUNT)]
#endif
public async Task<int> ExecuteGeoRadiusAsync() public async Task<int> ExecuteGeoRadiusAsync()
{ {
int total = 0; int total = 0;
...@@ -173,11 +165,7 @@ public async Task<int> ExecuteGeoRadiusAsync() ...@@ -173,11 +165,7 @@ public async Task<int> ExecuteGeoRadiusAsync()
/// <summary> /// <summary>
/// Run StringSet lots of times /// Run StringSet lots of times
/// </summary> /// </summary>
#if TEST_BASELINE [Benchmark(Description = "StringSet/s", OperationsPerInvoke = COUNT)]
[Benchmark(Description = "StringSet:v1/a", OperationsPerInvoke = COUNT)]
#else
[Benchmark(Description = "StringSet:v2/a", OperationsPerInvoke = COUNT)]
#endif
public void StringSet() public void StringSet()
{ {
for (int i = 0; i < COUNT; i++) for (int i = 0; i < COUNT; i++)
...@@ -189,11 +177,7 @@ public void StringSet() ...@@ -189,11 +177,7 @@ public void StringSet()
/// <summary> /// <summary>
/// Run StringGet lots of times /// Run StringGet lots of times
/// </summary> /// </summary>
#if TEST_BASELINE [Benchmark(Description = "StringGet/s", OperationsPerInvoke = COUNT)]
[Benchmark(Description = "StringGet:v1/a", OperationsPerInvoke = COUNT)]
#else
[Benchmark(Description = "StringGet:v2/a", OperationsPerInvoke = COUNT)]
#endif
public void StringGet() public void StringGet()
{ {
for (int i = 0; i < COUNT; i++) for (int i = 0; i < COUNT; i++)
...@@ -201,6 +185,33 @@ public void StringGet() ...@@ -201,6 +185,33 @@ public void StringGet()
db.StringGet(StringKey); db.StringGet(StringKey);
} }
} }
/// <summary>
/// Run HashGetAll lots of times
/// </summary>
[Benchmark(Description = "HashGetAll F+F/s", OperationsPerInvoke = COUNT)]
public void HashGetAll_FAF()
{
for (int i = 0; i < COUNT; i++)
{
db.HashGetAll(HashKey, CommandFlags.FireAndForget);
db.Ping(); // to wait for response
}
}
/// <summary>
/// Run HashGetAll lots of times
/// </summary>
[Benchmark(Description = "HashGetAll F+F/a", OperationsPerInvoke = COUNT)]
public async Task HashGetAllAsync_FAF()
{
for (int i = 0; i < COUNT; i++)
{
await db.HashGetAllAsync(HashKey, CommandFlags.FireAndForget);
await db.PingAsync(); // to wait for response
}
}
} }
#pragma warning disable CS1591 #pragma warning disable CS1591
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.11.1" /> <PackageReference Include="BenchmarkDotNet" Version="0.11.4" />
<PackageReference Include="StackExchange.Redis" Version="[1.2.7-alpha-00002]" /> <PackageReference Include="StackExchange.Redis" Version="[2.0.519]" />
</ItemGroup> </ItemGroup>
</Project> </Project>
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Pipelines.Sockets.Unofficial.Arenas;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace StackExchange.Redis.Tests namespace StackExchange.Redis.Tests
{ {
public class Parse : TestBase public class ParseTests : TestBase
{ {
public Parse(ITestOutputHelper output) : base(output) { } public ParseTests(ITestOutputHelper output) : base(output) { }
public static IEnumerable<object[]> GetTestData() public static IEnumerable<object[]> GetTestData()
{ {
...@@ -34,7 +35,10 @@ public static IEnumerable<object[]> GetTestData() ...@@ -34,7 +35,10 @@ public static IEnumerable<object[]> GetTestData()
public void ParseAsSingleChunk(string ascii, int expected) public void ParseAsSingleChunk(string ascii, int expected)
{ {
var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(ascii)); var buffer = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes(ascii));
ProcessMessages(buffer, expected); using (var arena = new Arena<RawResult>())
{
ProcessMessages(arena, buffer, expected);
}
} }
[Theory] [Theory]
...@@ -58,16 +62,19 @@ public void ParseAsLotsOfChunks(string ascii, int expected) ...@@ -58,16 +62,19 @@ public void ParseAsLotsOfChunks(string ascii, int expected)
} }
var buffer = new ReadOnlySequence<byte>(chain, 0, tail, 1); var buffer = new ReadOnlySequence<byte>(chain, 0, tail, 1);
Assert.Equal(bytes.Length, buffer.Length); Assert.Equal(bytes.Length, buffer.Length);
ProcessMessages(buffer, expected); using (var arena = new Arena<RawResult>())
{
ProcessMessages(arena, buffer, expected);
}
} }
private void ProcessMessages(ReadOnlySequence<byte> buffer, int expected) private void ProcessMessages(Arena<RawResult> arena, ReadOnlySequence<byte> buffer, int expected)
{ {
Writer.WriteLine($"chain: {buffer.Length}"); Writer.WriteLine($"chain: {buffer.Length}");
var reader = new BufferReader(buffer); var reader = new BufferReader(buffer);
RawResult result; RawResult result;
int found = 0; int found = 0;
while (!(result = PhysicalConnection.TryParseResult(buffer, ref reader, false, null, false)).IsNull) while (!(result = PhysicalConnection.TryParseResult(arena, buffer, ref reader, false, null, false)).IsNull)
{ {
Writer.WriteLine($"{result} - {result.GetString()}"); Writer.WriteLine($"{result} - {result.GetString()}");
found++; found++;
......
...@@ -14,6 +14,8 @@ public class TestInfoReplicationChecks : TestBase ...@@ -14,6 +14,8 @@ public class TestInfoReplicationChecks : TestBase
[Fact] [Fact]
public async Task Exec() public async Task Exec()
{ {
Skip.Inconclusive("need to think about CompletedSynchronously");
using(var conn = Create()) using(var conn = Create())
{ {
var parsed = ConfigurationOptions.Parse(conn.Configuration); var parsed = ConfigurationOptions.Parse(conn.Configuration);
......
...@@ -34,8 +34,6 @@ internal RedisRequest(in RawResult result) ...@@ -34,8 +34,6 @@ internal RedisRequest(in RawResult result)
Count = result.ItemsCount; Count = result.ItemsCount;
} }
internal void Recycle() => _inner.Recycle();
public RedisValue GetValue(int index) public RedisValue GetValue(int index)
=> _inner[index].AsRedisValue(); => _inner[index].AsRedisValue();
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Pipelines.Sockets.Unofficial; using Pipelines.Sockets.Unofficial;
using Pipelines.Sockets.Unofficial.Arenas;
namespace StackExchange.Redis.Server namespace StackExchange.Redis.Server
{ {
...@@ -241,6 +242,7 @@ protected void DoShutdown(ShutdownReason reason) ...@@ -241,6 +242,7 @@ protected void DoShutdown(ShutdownReason reason)
public void Dispose() => Dispose(true); public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
_arena.Dispose();
DoShutdown(ShutdownReason.ServerDisposed); DoShutdown(ShutdownReason.ServerDisposed);
} }
...@@ -363,10 +365,11 @@ void WritePrefix(PipeWriter ooutput, char pprefix) ...@@ -363,10 +365,11 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
} }
await output.FlushAsync().ConfigureAwait(false); await output.FlushAsync().ConfigureAwait(false);
} }
public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisRequest request)
private static bool TryParseRequest(Arena<RawResult> arena, ref ReadOnlySequence<byte> buffer, out RedisRequest request)
{ {
var reader = new BufferReader(buffer); var reader = new BufferReader(buffer);
var raw = PhysicalConnection.TryParseResult(in buffer, ref reader, false, null, true); var raw = PhysicalConnection.TryParseResult(arena, in buffer, ref reader, false, null, true);
if (raw.HasValue) if (raw.HasValue)
{ {
buffer = reader.SliceFromCurrent(); buffer = reader.SliceFromCurrent();
...@@ -377,6 +380,9 @@ public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisR ...@@ -377,6 +380,9 @@ public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisR
return false; return false;
} }
private readonly Arena<RawResult> _arena = new Arena<RawResult>();
public ValueTask<bool> TryProcessRequestAsync(ref ReadOnlySequence<byte> buffer, RedisClient client, PipeWriter output) public ValueTask<bool> TryProcessRequestAsync(ref ReadOnlySequence<byte> buffer, RedisClient client, PipeWriter output)
{ {
async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse) async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse)
...@@ -385,11 +391,11 @@ async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse) ...@@ -385,11 +391,11 @@ async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse)
rresponse.Recycle(); rresponse.Recycle();
return true; return true;
} }
if (!buffer.IsEmpty && TryParseRequest(ref buffer, out var request)) if (!buffer.IsEmpty && TryParseRequest(_arena, ref buffer, out var request))
{ {
TypedRedisValue response; TypedRedisValue response;
try { response = Execute(client, request); } try { response = Execute(client, request); }
finally { request.Recycle(); } finally { _arena.Reset(); }
var write = WriteResponseAsync(client, output, response); var write = WriteResponseAsync(client, output, response);
if (!write.IsCompletedSuccessfully) return Awaited(write, response); if (!write.IsCompletedSuccessfully) return Awaited(write, response);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment