11using System . Diagnostics . CodeAnalysis ;
2+ using Reloaded . Memory . Exceptions ;
23using Reloaded . Memory . Utilities ;
34#if NET7_0_OR_GREATER
45using System . Numerics ;
@@ -16,7 +17,7 @@ internal static class UnstableStringHash
1617{
1718 /// <summary>
1819 /// Faster hashcode for strings; but does not randomize between application runs.
19- /// Inspired by .NET Runtime's own implementation; combining unrolled djb-like and FNV-1 .
20+ /// Essentially a SIMD'ed FNV-1a .
2021 /// </summary>
2122 /// <param name="text">The string for which to get hash code for.</param>
2223 /// <remarks>
@@ -186,55 +187,151 @@ internal static unsafe UIntPtr UnstableHashVec256(this ReadOnlySpan<char> text)
186187 #endif
187188
188189 internal static unsafe UIntPtr UnstableHashNonVector ( this ReadOnlySpan < char > text )
190+ {
191+ // JIT will convert this to direct branch.
192+ switch ( sizeof ( nuint ) )
193+ {
194+ case 4 :
195+ return UnstableHashNonVector32 ( text ) ;
196+ case 8 :
197+ return UnstableHashNonVector64 ( text ) ;
198+ default :
199+ ThrowHelpers . ThrowArchitectureNotSupportedException ( ) ;
200+ return ( nuint ) 0 ;
201+ }
202+ }
203+
204+ internal static unsafe UIntPtr UnstableHashNonVector32 ( this ReadOnlySpan < char > text )
189205 {
190206 fixed ( char * src = & text . GetPinnableReference ( ) )
191207 {
192208 var length = text . Length ; // Span has no guarantee of null terminator.
193- nuint hash1 = ( 5381 << 16 ) + 5381 ;
209+ const ulong prime = 0x01000193 ;
210+ ulong hash1 = 0x811c9dc5 ;
194211 var hash2 = hash1 ;
195- var ptr = ( nuint * ) ( src ) ;
212+ var ptr = ( uint * ) ( src ) ;
196213
197214 // Non-vector accelerated version here.
198- // 32/64 byte loop
199- while ( length >= ( sizeof ( nuint ) / sizeof ( char ) ) * 8 )
200- {
201- length -= ( sizeof ( nuint ) / sizeof ( char ) ) * 8 ;
202- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 0 ] ;
203- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 1 ] ;
204- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 2 ] ;
205- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 3 ] ;
206- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 4 ] ;
207- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 5 ] ;
208- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 6 ] ;
209- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 7 ] ;
215+ // 32 byte loop
216+ while ( length >= ( sizeof ( uint ) / sizeof ( char ) ) * 8 )
217+ {
218+ length -= ( sizeof ( uint ) / sizeof ( char ) ) * 8 ;
219+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
220+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
221+ hash1 = ( hash1 ^ ptr [ 2 ] ) * prime ;
222+ hash2 = ( hash2 ^ ptr [ 3 ] ) * prime ;
223+ hash1 = ( hash1 ^ ptr [ 4 ] ) * prime ;
224+ hash2 = ( hash2 ^ ptr [ 5 ] ) * prime ;
225+ hash1 = ( hash1 ^ ptr [ 6 ] ) * prime ;
226+ hash2 = ( hash2 ^ ptr [ 7 ] ) * prime ;
210227 ptr += 8 ;
211228 }
212229
213- // 16/32 byte
214- if ( length >= ( sizeof ( nuint ) / sizeof ( char ) ) * 4 )
230+ // 16 byte
231+ if ( length >= ( sizeof ( uint ) / sizeof ( char ) ) * 4 )
215232 {
216- length -= ( sizeof ( nuint ) / sizeof ( char ) ) * 4 ;
217- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 0 ] ;
218- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 1 ] ;
219- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 2 ] ;
220- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 3 ] ;
233+ length -= ( sizeof ( uint ) / sizeof ( char ) ) * 4 ;
234+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
235+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
236+ hash1 = ( hash1 ^ ptr [ 2 ] ) * prime ;
237+ hash2 = ( hash2 ^ ptr [ 3 ] ) * prime ;
221238 ptr += 4 ;
222239 }
223240
224- // 8/16 byte
225- if ( length >= ( sizeof ( nuint ) / sizeof ( char ) ) * 2 )
241+ // 8 byte
242+ if ( length >= ( sizeof ( uint ) / sizeof ( char ) ) * 2 )
226243 {
227- length -= ( sizeof ( nuint ) / sizeof ( char ) ) * 2 ;
228- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 0 ] ;
229- hash2 = ( Polyfills . RotateLeft ( hash2 , 5 ) + hash2 ) ^ ptr [ 1 ] ;
244+ length -= ( sizeof ( uint ) / sizeof ( char ) ) * 2 ;
245+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
246+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
230247 ptr += 2 ;
231248 }
232249
233- // 4/8 byte
234- if ( length >= ( sizeof ( nuint ) / sizeof ( char ) ) )
235- hash1 = ( Polyfills . RotateLeft ( hash1 , 5 ) + hash1 ) ^ ptr [ 0 ] ;
250+ // 4 byte
251+ if ( length >= ( sizeof ( uint ) / sizeof ( char ) ) )
252+ {
253+ length -= ( sizeof ( uint ) / sizeof ( char ) ) ;
254+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
255+ ptr += 1 ;
256+ }
257+
258+ // 2 bytes potentially left
259+ var remainingPtr = ( char * ) ptr ;
260+ if ( length >= 1 )
261+ hash2 = ( hash2 ^ remainingPtr [ 0 ] ) * prime ;
236262
237- return hash1 + ( hash2 * 1566083941 ) ;
263+ return ( nuint ) ( hash1 + ( hash2 * 1566083941 ) ) ;
264+ }
265+ }
266+
267+ internal static unsafe UIntPtr UnstableHashNonVector64 ( this ReadOnlySpan < char > text )
268+ {
269+ fixed ( char * src = & text . GetPinnableReference ( ) )
270+ {
271+ var length = text . Length ; // Span has no guarantee of null terminator.
272+ const ulong prime = 0x00000100000001B3 ;
273+ ulong hash1 = 0xcbf29ce484222325 ;
274+ var hash2 = hash1 ;
275+ var ptr = ( ulong * ) ( src ) ;
276+
277+ // Non-vector accelerated version here.
278+ // 64 byte loop
279+ while ( length >= ( sizeof ( ulong ) / sizeof ( char ) ) * 8 )
280+ {
281+ length -= ( sizeof ( ulong ) / sizeof ( char ) ) * 8 ;
282+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
283+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
284+ hash1 = ( hash1 ^ ptr [ 2 ] ) * prime ;
285+ hash2 = ( hash2 ^ ptr [ 3 ] ) * prime ;
286+ hash1 = ( hash1 ^ ptr [ 4 ] ) * prime ;
287+ hash2 = ( hash2 ^ ptr [ 5 ] ) * prime ;
288+ hash1 = ( hash1 ^ ptr [ 6 ] ) * prime ;
289+ hash2 = ( hash2 ^ ptr [ 7 ] ) * prime ;
290+ ptr += 8 ;
291+ }
292+
293+ // 32 byte
294+ if ( length >= ( sizeof ( ulong ) / sizeof ( char ) ) * 4 )
295+ {
296+ length -= ( sizeof ( ulong ) / sizeof ( char ) ) * 4 ;
297+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
298+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
299+ hash1 = ( hash1 ^ ptr [ 2 ] ) * prime ;
300+ hash2 = ( hash2 ^ ptr [ 3 ] ) * prime ;
301+ ptr += 4 ;
302+ }
303+
304+ // 16 byte
305+ if ( length >= ( sizeof ( ulong ) / sizeof ( char ) ) * 2 )
306+ {
307+ length -= ( sizeof ( ulong ) / sizeof ( char ) ) * 2 ;
308+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
309+ hash2 = ( hash2 ^ ptr [ 1 ] ) * prime ;
310+ ptr += 2 ;
311+ }
312+
313+ // 8 byte
314+ if ( length >= ( sizeof ( ulong ) / sizeof ( char ) ) )
315+ {
316+ length -= ( sizeof ( ulong ) / sizeof ( char ) ) ;
317+ hash1 = ( hash1 ^ ptr [ 0 ] ) * prime ;
318+ ptr += 1 ;
319+ }
320+
321+ // 2/4/6 bytes left
322+ var remainingPtr = ( char * ) ptr ;
323+ if ( length >= 2 )
324+ {
325+ length -= 2 ;
326+ hash1 = ( hash1 ^ remainingPtr [ 0 ] ) * prime ;
327+ hash2 = ( hash2 ^ remainingPtr [ 1 ] ) * prime ;
328+ remainingPtr += 2 ;
329+ }
330+
331+ if ( length >= 1 )
332+ hash2 = ( hash2 ^ remainingPtr [ 0 ] ) * prime ;
333+
334+ return ( nuint ) ( hash1 + ( hash2 * 1566083941 ) ) ;
238335 }
239336 }
240337}
0 commit comments