Skip to content

Commit c36e26d

Browse files
teo-tsirpanistannergoodingstephentoub
authored
Optimize HashCode.AddBytes for inputs larger than 16 bytes. (#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>
1 parent 879f201 commit c36e26d

File tree

1 file changed

+56
-1
lines changed
  • src/libraries/System.Private.CoreLib/src/System

1 file changed

+56
-1
lines changed

src/libraries/System.Private.CoreLib/src/System/HashCode.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
4242

4343
using System.Collections.Generic;
4444
using System.ComponentModel;
45+
using System.Diagnostics;
4546
using System.Numerics;
4647
using System.Runtime.CompilerServices;
4748
using System.Runtime.InteropServices;
@@ -319,8 +320,62 @@ public void AddBytes(ReadOnlySpan<byte> value)
319320
ref byte pos = ref MemoryMarshal.GetReference(value);
320321
ref byte end = ref Unsafe.Add(ref pos, value.Length);
321322

323+
if (value.Length < (sizeof(int) * 4))
324+
{
325+
goto Small;
326+
}
327+
328+
// Usually Add calls Initialize but if we haven't used HashCode before it won't have been called.
329+
if (_length == 0)
330+
{
331+
Initialize(out _v1, out _v2, out _v3, out _v4);
332+
}
333+
else
334+
{
335+
// If we have at least 16 bytes to hash, we can add them in 16-byte batches,
336+
// but we first have to add enough data to flush any queued values.
337+
switch (_length % 4)
338+
{
339+
case 1:
340+
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
341+
Add(Unsafe.ReadUnaligned<int>(ref pos));
342+
pos = ref Unsafe.Add(ref pos, sizeof(int));
343+
goto case 2;
344+
case 2:
345+
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
346+
Add(Unsafe.ReadUnaligned<int>(ref pos));
347+
pos = ref Unsafe.Add(ref pos, sizeof(int));
348+
goto case 3;
349+
case 3:
350+
Debug.Assert(Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int));
351+
Add(Unsafe.ReadUnaligned<int>(ref pos));
352+
pos = ref Unsafe.Add(ref pos, sizeof(int));
353+
break;
354+
}
355+
}
356+
357+
// With the queue clear, we add sixteen bytes at a time until the input has fewer than sixteen bytes remaining.
358+
// We first have to round the end pointer to the nearest 16-byte block from the offset. This makes the loop's condition simpler.
359+
ref byte blockEnd = ref Unsafe.Subtract(ref end, Unsafe.ByteOffset(ref pos, ref end) % (sizeof(int) * 4));
360+
while (Unsafe.IsAddressLessThan(ref pos, ref blockEnd))
361+
{
362+
Debug.Assert(Unsafe.ByteOffset(ref pos, ref blockEnd) >= (sizeof(int) * 4));
363+
uint v1 = Unsafe.ReadUnaligned<uint>(ref pos);
364+
_v1 = Round(_v1, v1);
365+
uint v2 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 1));
366+
_v2 = Round(_v2, v2);
367+
uint v3 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 2));
368+
_v3 = Round(_v3, v3);
369+
uint v4 = Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref pos, sizeof(int) * 3));
370+
_v4 = Round(_v4, v4);
371+
372+
_length += 4;
373+
pos = ref Unsafe.Add(ref pos, sizeof(int) * 4);
374+
}
375+
376+
Small:
322377
// Add four bytes at a time until the input has fewer than four bytes remaining.
323-
while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int))
378+
while (Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int))
324379
{
325380
Add(Unsafe.ReadUnaligned<int>(ref pos));
326381
pos = ref Unsafe.Add(ref pos, sizeof(int));

0 commit comments

Comments
 (0)