22using System . Buffers ;
33using System . Buffers . Binary ;
44using System . Diagnostics ;
5+ using System . Diagnostics . CodeAnalysis ;
56using System . Runtime . CompilerServices ;
67using System . Runtime . InteropServices ;
78
8- namespace StackExchange . Redis ;
9+ namespace RESPite ;
910
1011/// <summary>
1112/// This type is intended to provide fast hashing functions for small strings, for example well-known
@@ -15,54 +16,125 @@ namespace StackExchange.Redis;
1516/// <remarks>See HastHashGenerator.md for more information and intended usage.</remarks>
1617[ AttributeUsage ( AttributeTargets . Class , AllowMultiple = false , Inherited = false ) ]
1718[ Conditional ( "DEBUG" ) ] // evaporate in release
18- internal sealed class FastHashAttribute ( string token = "" ) : Attribute
19+ [ Experimental ( Experiments . Respite , UrlFormat = Experiments . UrlFormat ) ]
20+ public sealed class FastHashAttribute ( string token = "" ) : Attribute
1921{
2022 public string Token => token ;
2123}
2224
23- internal static class FastHash
25+ [ Experimental ( Experiments . Respite , UrlFormat = Experiments . UrlFormat ) ]
26+ public readonly struct FastHash
2427{
25- /* not sure we need this, but: retain for reference
28+ private readonly long _hashCI ;
29+ private readonly long _hashCS ;
30+ private readonly ReadOnlyMemory < byte > _value ;
31+
32+ public FastHash ( ReadOnlySpan < byte > value ) : this ( ( ReadOnlyMemory < byte > ) value . ToArray ( ) ) { }
33+ public FastHash ( ReadOnlyMemory < byte > value )
34+ {
35+ _value = value ;
36+ var span = value . Span ;
37+ _hashCI = HashCI ( span ) ;
38+ _hashCS = HashCS ( span ) ;
39+ }
2640
27- // Perform case-insensitive hash by masking (X and x differ by only 1 bit); this halves
28- // our entropy, but is still useful when case doesn't matter.
2941 private const long CaseMask = ~ 0x2020202020202020 ;
3042
31- public static long Hash64CI(this ReadOnlySequence<byte> value)
32- => value.Hash64() & CaseMask;
33- public static long Hash64CI(this scoped ReadOnlySpan<byte> value)
34- => value.Hash64() & CaseMask;
35- */
43+ public bool IsCS ( ReadOnlySpan < byte > value ) => IsCS ( HashCS ( value ) , value ) ;
44+
45+ public bool IsCS ( long hash , ReadOnlySpan < byte > value )
46+ {
47+ var len = _value . Length ;
48+ if ( hash != _hashCS | ( value . Length != len ) ) return false ;
49+ return len <= MaxBytesHashIsEqualityCS || EqualsCS ( _value . Span , value ) ;
50+ }
51+
52+ public bool IsCI ( ReadOnlySpan < byte > value ) => IsCI ( HashCI ( value ) , value ) ;
53+ public bool IsCI ( long hash , ReadOnlySpan < byte > value )
54+ {
55+ var len = _value . Length ;
56+ if ( hash != _hashCI | ( value . Length != len ) ) return false ;
57+ if ( len <= MaxBytesHashIsEqualityCS && HashCS ( value ) == _hashCS ) return true ;
58+ return EqualsCI ( _value . Span , value ) ;
59+ }
3660
37- public static long Hash64 ( this ReadOnlySequence < byte > value )
61+ public static long HashCS ( ReadOnlySequence < byte > value )
3862 {
3963#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
4064 var first = value . FirstSpan ;
4165#else
4266 var first = value . First . Span ;
4367#endif
44- return first . Length >= sizeof ( long ) || value . IsSingleSegment
45- ? first . Hash64 ( ) : SlowHash64 ( value ) ;
68+ return first . Length >= MaxBytesHashed || value . IsSingleSegment
69+ ? HashCS ( first ) : SlowHashCS ( value ) ;
4670
47- static long SlowHash64 ( ReadOnlySequence < byte > value )
71+ static long SlowHashCS ( ReadOnlySequence < byte > value )
4872 {
49- Span < byte > buffer = stackalloc byte [ sizeof ( long ) ] ;
50- if ( value . Length < sizeof ( long ) )
73+ Span < byte > buffer = stackalloc byte [ MaxBytesHashed ] ;
74+ var len = value . Length ;
75+ if ( len <= MaxBytesHashed )
5176 {
5277 value . CopyTo ( buffer ) ;
53- buffer . Slice ( ( int ) value . Length ) . Clear ( ) ;
78+ buffer = buffer . Slice ( 0 , ( int ) len ) ;
5479 }
5580 else
5681 {
57- value . Slice ( 0 , sizeof ( long ) ) . CopyTo ( buffer ) ;
82+ value . Slice ( 0 , MaxBytesHashed ) . CopyTo ( buffer ) ;
5883 }
59- return BitConverter . IsLittleEndian
60- ? Unsafe . ReadUnaligned < long > ( ref MemoryMarshal . GetReference ( buffer ) )
61- : BinaryPrimitives . ReadInt64LittleEndian ( buffer ) ;
84+ return HashCS ( buffer ) ;
6285 }
6386 }
6487
65- public static long Hash64 ( this scoped ReadOnlySpan < byte > value )
88+ internal const int MaxBytesHashIsEqualityCS = sizeof ( long ) , MaxBytesHashed = sizeof ( long ) ;
89+
90+ public static bool EqualsCS ( ReadOnlySpan < byte > first , ReadOnlySpan < byte > second )
91+ {
92+ var len = first . Length ;
93+ if ( len != second . Length ) return false ;
94+ // for very short values, the CS hash performs CS equality
95+ return len <= MaxBytesHashIsEqualityCS ? HashCS ( first ) == HashCS ( second ) : first . SequenceEqual ( second ) ;
96+ }
97+
98+ public static unsafe bool EqualsCI ( ReadOnlySpan < byte > first , ReadOnlySpan < byte > second )
99+ {
100+ var len = first . Length ;
101+ if ( len != second . Length ) return false ;
102+ // for very short values, the CS hash performs CS equality; check that first
103+ if ( len <= MaxBytesHashIsEqualityCS && HashCS ( first ) == HashCS ( second ) ) return true ;
104+
105+ // OK, don't be clever (SIMD, etc); the purpose of FashHash is to compare RESP key tokens, which are
106+ // typically relatively short, think 3-20 bytes. That wouldn't even touch a SIMD vector, so:
107+ // just loop (the exact thing we'd need to do *anyway* in a SIMD implementation, to mop up the non-SIMD
108+ // trailing bytes).
109+ fixed ( byte * firstPtr = & MemoryMarshal . GetReference ( first ) )
110+ {
111+ fixed ( byte * secondPtr = & MemoryMarshal . GetReference ( second ) )
112+ {
113+ const int CS_MASK = ~ 0x20 ;
114+ for ( int i = 0 ; i < len ; i ++ )
115+ {
116+ byte x = firstPtr [ i ] ;
117+ var xCI = x & CS_MASK ;
118+ if ( xCI >= 'A' & xCI <= 'Z' )
119+ {
120+ // alpha mismatch
121+ if ( xCI != ( secondPtr [ i ] & CS_MASK ) ) return false ;
122+ }
123+ else if ( x != secondPtr [ i ] )
124+ {
125+ // non-alpha mismatch
126+ return false ;
127+ }
128+ }
129+ return true ;
130+ }
131+ }
132+ }
133+
134+ public static long HashCI ( scoped ReadOnlySpan < byte > value )
135+ => HashCS ( value ) & CaseMask ;
136+
137+ public static long HashCS ( scoped ReadOnlySpan < byte > value )
66138 {
67139 if ( BitConverter . IsLittleEndian )
68140 {
0 commit comments