Skip to content

Commit

Permalink
Optimize HashCode.AddBytes for inputs larger than 16 bytes. (dotnet…
Browse files Browse the repository at this point in the history
…#70095)

* Optimize `HashCode.AddBytes` for inputs larger than 16 bytes.

* Address PR feedback, refactor control flow and inline UnsafeAddMany.

Co-authored-by: Tanner Gooding <tagoo@outlook.com>

* Add more asserts before memory reads and move one inside the main loop.

* Address PR feedback.

Co-authored-by: Stephen Toub <stoub@microsoft.com>

Co-authored-by: Tanner Gooding <tagoo@outlook.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
  • Loading branch information
3 people committed Jun 29, 2022
1 parent 879f201 commit c36e26d
Showing 1 changed file with 56 additions and 1 deletion.
57 changes: 56 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -319,8 +320,62 @@ public void AddBytes(ReadOnlySpan<byte> value)
ref byte pos = ref MemoryMarshal.GetReference(value);
ref byte end = ref Unsafe.Add(ref pos, value.Length);

if (value.Length < (sizeof(int) * 4))
{
goto Small;
}

// Usually Add calls Initialize but if we haven't used HashCode before it won't have been called.
if (_length == 0)
{
Initialize(out _v1, out _v2, out _v3, out _v4);
}
else
{
// If we have at least 16 bytes to hash, we can add them in 16-byte batches,
// but we first have to add enough data to flush any queued values.
switch (_length % 4)
{
case 1:
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
Add(Unsafe.ReadUnaligned<int>(ref pos));
pos = ref Unsafe.Add(ref pos, sizeof(int));
goto case 2;
case 2:
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
Add(Unsafe.ReadUnaligned<int>(ref pos));
pos = ref Unsafe.Add(ref pos, sizeof(int));
goto case 3;
case 3:
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
Add(Unsafe.ReadUnaligned<int>(ref pos));
pos = ref Unsafe.Add(ref pos, sizeof(int));
break;
}
}

// With the queue clear, we add sixteen bytes at a time until the input has fewer than sixteen bytes remaining.
// We first have to round the end pointer to the nearest 16-byte block from the offset. This makes the loop's condition simpler.
ref byte blockEnd = ref Unsafe.Subtract(ref end, Unsafe.ByteOffset(ref pos, ref end) % (sizeof(int) * 4));
while (Unsafe.IsAddressLessThan(ref pos, ref blockEnd))
{
Debug.Assert(Unsafe.ByteOffset(ref pos, ref blockEnd) >= (sizeof(int) * 4));
uint v1 = Unsafe.ReadUnaligned<uint>(ref pos);
_v1 = Round(_v1, v1);
uint v2 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 1));
_v2 = Round(_v2, v2);
uint v3 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 2));
_v3 = Round(_v3, v3);
uint v4 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 3));
_v4 = Round(_v4, v4);

_length += 4;
pos = ref Unsafe.Add(ref pos, sizeof(int) * 4);
}

Small:
// Add four bytes at a time until the input has fewer than four bytes remaining.
while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int))
while (Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int))
{
Add(Unsafe.ReadUnaligned<int>(ref pos));
pos = ref Unsafe.Add(ref pos, sizeof(int));
Expand Down

0 comments on commit c36e26d

Please sign in to comment.