Commit e48955c6 authored by Marc Gravell's avatar Marc Gravell

more efficient CommandBytes; fixed size, don't store hashcode, use ulong for all internals

parent 20fcc512
using System;
using System.Buffers;
using System.Text;
namespace StackExchange.Redis
{
public unsafe struct CommandBytes : IEquatable<CommandBytes>
public readonly struct CommandBytes : IEquatable<CommandBytes>
{
public override int GetHashCode() => _hashcode;
public override string ToString()
private static Encoding Encoding => Encoding.UTF8;
// Uses [n=4] x UInt64 values to store a command payload,
// allowing allocation free storage and efficient
// equality tests. If you're glancing at this and thinking
// "that's what fixed buffers are for", please see:
// https://github.com/dotnet/coreclr/issues/19149
//
// note: this tries to use case insensitive comparison
private readonly ulong _0, _1, _2;
private const int ChunkLength = 3; // must reflect qty above
public const int MaxLength = (ChunkLength * sizeof(ulong)) - 1;
public override int GetHashCode()
{
var hashCode = -1923861349;
hashCode = hashCode * -1521134295 + _0.GetHashCode();
hashCode = hashCode * -1521134295 + _1.GetHashCode();
hashCode = hashCode * -1521134295 + _2.GetHashCode();
return hashCode;
}
public override bool Equals(object obj) => obj is CommandBytes cb && Equals(cb);
public bool Equals(CommandBytes value) => _0 == value._0 && _1 == value._1 && _2 == value._2;
public static bool operator == (CommandBytes x, CommandBytes y) => x.Equals(y);
public static bool operator !=(CommandBytes x, CommandBytes y) => !x.Equals(y);
public override unsafe string ToString()
{
fixed (byte* ptr = _bytes)
fixed (ulong* uPtr = &_0)
{
return Encoding.UTF8.GetString(ptr, Length);
var bPtr = (byte*)uPtr;
int len = *bPtr;
return len == 0 ? "" : Encoding.GetString(bPtr + 1, *bPtr);
}
}
public byte this[int index]
public unsafe int Length
{
get
{
if (index < 0 || index >= Length) throw new IndexOutOfRangeException();
fixed (byte* ptr = _bytes)
fixed (ulong* uPtr = &_0)
{
return ptr[index];
return *(byte*)uPtr;
}
}
}
public const int MaxLength = 32; // mut be multiple of 8
public int Length { get; }
readonly int _hashcode;
fixed byte _bytes[MaxLength];
public CommandBytes(string value)
public unsafe byte this[int index]
{
value = value.ToLowerInvariant();
Length = Encoding.UTF8.GetByteCount(value);
if (Length > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeed");
fixed (byte* bPtr = _bytes)
get
{
Clear((long*)bPtr);
fixed (char* cPtr = value)
fixed (ulong* uPtr = &_0)
{
Encoding.UTF8.GetBytes(cPtr, value.Length, bPtr, Length);
byte* bPtr = (byte*)uPtr;
int len = *bPtr;
if (index < 0 || index >= len) throw new IndexOutOfRangeException();
return bPtr[index + 1];
}
_hashcode = GetHashCode(bPtr, Length);
}
}
public override bool Equals(object obj) => obj is CommandBytes cb && Equals(cb);
public bool Equals(CommandBytes value)
public unsafe CommandBytes(string value)
{
if (_hashcode != value._hashcode || Length != value.Length)
return false;
fixed (byte* thisB = _bytes)
var len = Encoding.GetByteCount(value);
if (len > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeed");
_0 = _1 = _2 = 0L;
fixed (ulong* uPtr = &_0)
{
var thisL = (long*)thisB;
var otherL = (long*)value._bytes;
int chunks = (Length + 7) >> 3;
for (int i = 0; i < chunks; i++)
byte* bPtr = (byte*)uPtr;
fixed (char* cPtr = value)
{
if (thisL[i] != otherL[i]) return false;
len = Encoding.GetBytes(cPtr, value.Length, bPtr + 1, MaxLength);
}
*bPtr = (byte)LowerCasify(len, bPtr + 1);
}
return true;
}
private static void Clear(long* ptr)
{
for (int i = 0; i < (MaxLength >> 3) ; i++)
public unsafe CommandBytes(ReadOnlySpan<byte> value)
{
if (value.Length > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeed");
_0 = _1 = _2 = 0L;
fixed (ulong* uPtr = &_0)
{
ptr[i] = 0;
byte* bPtr = (byte*)uPtr;
value.CopyTo(new Span<byte>(bPtr + 1, value.Length));
*bPtr = (byte)LowerCasify(value.Length, bPtr + 1);
}
}
public CommandBytes(ReadOnlySpan<byte> value)
public unsafe CommandBytes(ReadOnlySequence<byte> value)
{
Length = value.Length;
if (Length > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeed");
fixed (byte* bPtr = _bytes)
if (value.Length > MaxLength) throw new ArgumentOutOfRangeException("Maximum command length exceeed");
int len = unchecked((int)value.Length);
_0 = _1 = _2 = 0L;
fixed (ulong* uPtr = &_0)
{
Clear((long*)bPtr);
for (int i = 0; i < value.Length; i++)
byte* bPtr = (byte*)uPtr;
var target = new Span<byte>(bPtr + 1, len);
if (value.IsSingleSegment)
{
bPtr[i] = ToLowerInvariant(value[i]);
value.First.Span.CopyTo(target);
}
_hashcode = GetHashCode(bPtr, Length);
else
{
foreach (var segment in value)
{
segment.Span.CopyTo(target);
target = target.Slice(segment.Length);
}
}
*bPtr = (byte)LowerCasify(len, bPtr + 1);
}
}
static int GetHashCode(byte* ptr, int count)
private unsafe int LowerCasify(int len, byte* bPtr)
{
var hc = count;
for (int i = 0; i < count; i++)
const ulong HighBits = 0x8080808080808080;
if (((_0 | _1 | _2) & HighBits) == 0)
{
// no unicode; use ASCII bit bricks
for (int i = 0; i < len; i++)
{
*bPtr = ToLowerInvariantAscii(*bPtr++);
}
return len;
}
else
{
hc = (hc * -13547) + ptr[i];
return LowerCasifyUnicode(len, bPtr);
}
return hc;
}
static byte ToLowerInvariant(byte b) => b >= 'A' && b <= 'Z' ? (byte)(b | 32) : b;
internal byte[] ToArray()
private static unsafe int LowerCasifyUnicode(int oldLen, byte* bPtr)
{
const int MaxChars = ChunkLength * sizeof(ulong); // leave rounded up; helps stackalloc
char* workspace = stackalloc char[MaxChars];
int charCount = Encoding.GetChars(bPtr, oldLen, workspace, MaxChars);
char* c = workspace;
for (int i = 0; i < charCount; i++) *c = char.ToLowerInvariant((*c++));
int newLen = Encoding.GetBytes(workspace, charCount, bPtr, MaxLength);
// don't forget to zero any shrink
for (int i = newLen; i < oldLen; i++) bPtr[i] = 0;
return newLen;
}
static byte ToLowerInvariantAscii(byte b) => b >= 'A' && b <= 'Z' ? (byte)(b | 32) : b;
internal unsafe byte[] ToArray()
{
fixed (byte* ptr = _bytes)
fixed (ulong* uPtr = &_0)
{
return new Span<byte>(ptr, Length).ToArray();
byte* bPtr = (byte*)uPtr;
return new Span<byte>(bPtr + 1, *bPtr).ToArray();
}
}
}
......
......@@ -59,25 +59,7 @@ internal bool TryGetCommandBytes(int i, out CommandBytes command)
return false;
}
if (payload.Length == 0)
{
command = default;
}
else if (payload.IsSingleSegment)
{
command = new CommandBytes(payload.First.Span);
}
else
{
Span<byte> span = stackalloc byte[CommandBytes.MaxLength];
var sliced = span;
foreach (var segment in payload)
{
segment.Span.CopyTo(sliced);
sliced = sliced.Slice(segment.Length);
}
command = new CommandBytes(span.Slice(0, (int)payload.Length));
}
command = payload.IsEmpty ? default : new CommandBytes(payload);
return true;
}
}
......
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