Skip to content

Commit e18d19e

Browse files
committed
Changed: NonVector uses nuint sized FNV-1a for Better Distribution
1 parent bebac5d commit e18d19e

3 files changed

Lines changed: 200 additions & 126 deletions

File tree

src/Reloaded.Memory/Internals/Algorithms/UnstableStringHash.cs

Lines changed: 128 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using Reloaded.Memory.Exceptions;
23
using Reloaded.Memory.Utilities;
34
#if NET7_0_OR_GREATER
45
using 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

Comments
 (0)