Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Buffers;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;

namespace System.Collections.Frozen
Expand All @@ -20,63 +19,56 @@ internal static class Hashing
private const uint Hash1Start = (5381 << 16) + 5381;
private const uint Factor = 1_566_083_941;

public static unsafe int GetHashCodeOrdinal(ReadOnlySpan<char> s)
public static int GetHashCodeOrdinal(ReadOnlySpan<char> s)
{
int length = s.Length;
fixed (char* src = &MemoryMarshal.GetReference(s))
uint hash1, hash2;
switch (s.Length)
{
uint hash1, hash2;
switch (length)
{
case 0:
return (int)(Hash1Start + unchecked(Hash1Start * Factor));

case 1:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ src[0];
return (int)(Hash1Start + (hash2 * Factor));

case 2:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ src[0];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ src[1];
return (int)(Hash1Start + (hash2 * Factor));

case 3:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ src[0];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ src[1];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ src[2];
return (int)(Hash1Start + (hash2 * Factor));

case 4:
hash1 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ ((uint*)src)[0];
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ ((uint*)src)[1];
return (int)(hash1 + (hash2 * Factor));

default:
hash1 = Hash1Start;
hash2 = hash1;

uint* ptrUInt32 = (uint*)src;
while (length >= 4)
{
hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ ptrUInt32[0];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ptrUInt32[1];
ptrUInt32 += 2;
length -= 4;
}

char* ptrChar = (char*)ptrUInt32;
while (length-- > 0)
{
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ *ptrChar++;
}

return (int)(hash1 + (hash2 * Factor));
}
case 0:
return (int)(Hash1Start + unchecked(Hash1Start * Factor));

case 1:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ s[0];
return (int)(Hash1Start + (hash2 * Factor));

case 2:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ s[0];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ s[1];
return (int)(Hash1Start + (hash2 * Factor));

case 3:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ s[0];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ s[1];
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ s[2];
return (int)(Hash1Start + (hash2 * Factor));

case 4:
hash1 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ ((uint)s[0] | ((uint)s[1] << 16));
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ ((uint)s[2] | ((uint)s[3] << 16));
return (int)(hash1 + (hash2 * Factor));
Comment thread
EgorBo marked this conversation as resolved.

default:
hash1 = Hash1Start;
hash2 = hash1;

int i = 0;
for (; i <= s.Length - 4; i += 4)
{
hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ ((uint)s[i] | ((uint)s[i + 1] << 16));
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ ((uint)s[i + 2] | ((uint)s[i + 3] << 16));
}

for (; i < s.Length; i++)
{
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ s[i];
}
Comment thread
EgorBo marked this conversation as resolved.

return (int)(hash1 + (hash2 * Factor));
}
}

// useful if the string only contains ASCII characters
public static unsafe int GetHashCodeOrdinalIgnoreCaseAscii(ReadOnlySpan<char> s)
public static int GetHashCodeOrdinalIgnoreCaseAscii(ReadOnlySpan<char> s)
{
Debug.Assert(Ascii.IsValid(s));

Expand All @@ -86,57 +78,49 @@ public static unsafe int GetHashCodeOrdinalIgnoreCaseAscii(ReadOnlySpan<char> s)
const uint LowercaseChar = 0x20u;
const uint LowercaseUInt32 = 0x0020_0020u;

int length = s.Length;
fixed (char* src = &MemoryMarshal.GetReference(s))
uint hash1, hash2;
switch (s.Length)
{
uint hash1, hash2;
switch (length)
{
case 0:
return (int)(Hash1Start + unchecked(Hash1Start * Factor));

case 1:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (src[0] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 2:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (src[0] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (src[1] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 3:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (src[0] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (src[1] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (src[2] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 4:
hash1 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (((uint*)src)[0] | LowercaseUInt32);
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (((uint*)src)[1] | LowercaseUInt32);
return (int)(hash1 + (hash2 * Factor));

default:
hash1 = Hash1Start;
hash2 = hash1;

uint* ptrUInt32 = (uint*)src;
while (length >= 4)
{
hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ (ptrUInt32[0] | LowercaseUInt32);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (ptrUInt32[1] | LowercaseUInt32);
ptrUInt32 += 2;
length -= 4;
}

char* ptrChar = (char*)ptrUInt32;
while (length-- > 0)
{
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (*ptrChar | LowercaseUInt32);
Copy link
Copy Markdown
Member

@EgorBo EgorBo Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a copy-paste issue in the previous code - the single-char loop uses LowercaseUInt32 (for 2 chars at a time) instead of LowercaseChar. Not a correctness issue

ptrChar++;
}

return (int)(hash1 + (hash2 * Factor));
}
case 0:
return (int)(Hash1Start + unchecked(Hash1Start * Factor));

case 1:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (s[0] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 2:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (s[0] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (s[1] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 3:
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (s[0] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (s[1] | LowercaseChar);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (s[2] | LowercaseChar);
return (int)(Hash1Start + (hash2 * Factor));

case 4:
hash1 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (((uint)s[0] | ((uint)s[1] << 16)) | LowercaseUInt32);
hash2 = (BitOperations.RotateLeft(Hash1Start, 5) + Hash1Start) ^ (((uint)s[2] | ((uint)s[3] << 16)) | LowercaseUInt32);
return (int)(hash1 + (hash2 * Factor));
Comment thread
EgorBo marked this conversation as resolved.

default:
hash1 = Hash1Start;
hash2 = hash1;

int i = 0;
for (; i <= s.Length - 4; i += 4)
{
hash1 = (BitOperations.RotateLeft(hash1, 5) + hash1) ^ (((uint)s[i] | ((uint)s[i + 1] << 16)) | LowercaseUInt32);
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (((uint)s[i + 2] | ((uint)s[i + 3] << 16)) | LowercaseUInt32);
}

for (; i < s.Length; i++)
{
hash2 = (BitOperations.RotateLeft(hash2, 5) + hash2) ^ (s[i] | LowercaseChar);
}

return (int)(hash1 + (hash2 * Factor));
}
}

Expand Down
Loading