// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Numerics; using System.Runtime.CompilerServices; namespace CommunityToolkit.HighPerformance.Helpers.Internals; /// partial class SpanHelper { /// /// Calculates the djb2 hash for the target sequence of items of a given type. /// /// The type of items to hash. /// The reference to the target memory area to hash. /// The number of items to hash. /// The Djb2 value for the input sequence of items. public static int GetDjb2HashCode(ref T r0, nint length) where T : notnull { int hash = 5381; nint offset = 0; while (length >= 8) { // Doing a left shift by 5 and adding is equivalent to multiplying by 33. // This is preferred for performance reasons, as when working with integer // values most CPUs have higher latency for multiplication operations // compared to a simple shift and add. For more info on this, see the // details for imul, shl, add: https://gmplib.org/~tege/x86-timing.pdf. hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 4).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 5).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 6).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 7).GetHashCode()); length -= 8; offset += 8; } if (length >= 4) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode()); hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode()); length -= 4; offset += 4; } while (length > 0) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode()); length -= 1; offset += 1; } return hash; } /// /// Gets a content hash from a given memory area. /// /// A reference to the start of the memory area. /// The size in bytes of the memory area. /// The hash code for the contents of the source memory area. /// /// While this method is similar to and can in some cases /// produce the same output for a given memory area, it is not guaranteed to always be that way. /// This is because this method can use SIMD instructions if possible, which can cause a computed /// hash to differ for the same data, if processed on different machines with different CPU features. /// The advantage of this method is that when SIMD instructions are available, it performs much /// faster than , as it can parallelize much of the workload. /// public static unsafe int GetDjb2LikeByteHash(ref byte r0, nint length) { int hash = 5381; nint offset = 0; // Check whether SIMD instructions are supported, and also check // whether we have enough data to perform at least one unrolled // iteration of the vectorized path. This heuristics is to balance // the overhead of loading the constant values in the two registers, // and the final loop to combine the partial hash values. // Note that even when we use the vectorized path we don't need to do // any preprocessing to try to get memory aligned, as that would cause // the hash codes to potentially be different for the same data. if (Vector.IsHardwareAccelerated && length >= (Vector.Count << 3)) { Vector vh = new(5381); Vector v33 = new(33); // First vectorized loop, with 8 unrolled iterations. // Assuming 256-bit registers (AVX2), a total of 256 bytes are processed // per iteration, with the partial hashes being accumulated for later use. while (length >= (Vector.Count << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 0)); Vector vi0 = Unsafe.ReadUnaligned>(ref ri0); Vector vp0 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp0, vi0); ref byte ri1 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 1)); Vector vi1 = Unsafe.ReadUnaligned>(ref ri1); Vector vp1 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp1, vi1); ref byte ri2 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 2)); Vector vi2 = Unsafe.ReadUnaligned>(ref ri2); Vector vp2 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp2, vi2); ref byte ri3 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 3)); Vector vi3 = Unsafe.ReadUnaligned>(ref ri3); Vector vp3 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp3, vi3); ref byte ri4 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 4)); Vector vi4 = Unsafe.ReadUnaligned>(ref ri4); Vector vp4 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp4, vi4); ref byte ri5 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 5)); Vector vi5 = Unsafe.ReadUnaligned>(ref ri5); Vector vp5 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp5, vi5); ref byte ri6 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 6)); Vector vi6 = Unsafe.ReadUnaligned>(ref ri6); Vector vp6 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp6, vi6); ref byte ri7 = ref Unsafe.Add(ref r0, offset + (Vector.Count * 7)); Vector vi7 = Unsafe.ReadUnaligned>(ref ri7); Vector vp7 = Vector.Multiply(vh, v33); vh = Vector.Xor(vp7, vi7); length -= Vector.Count << 3; offset += Vector.Count << 3; } // When this loop is reached, there are up to 255 bytes left (on AVX2). // Each iteration processed an additional 32 bytes and accumulates the results. while (length >= Vector.Count) { ref byte ri = ref Unsafe.Add(ref r0, offset); Vector vi = Unsafe.ReadUnaligned>(ref ri); Vector vp = Vector.Multiply(vh, v33); vh = Vector.Xor(vp, vi); length -= Vector.Count; offset += Vector.Count; } // Combine the partial hash values in each position. // The loop below should automatically be unrolled by the JIT. for (int j = 0; j < Vector.Count; j++) { hash = unchecked(((hash << 5) + hash) ^ vh[j]); } } else { // Only use the loop working with 64-bit values if we are on a // 64-bit processor, otherwise the result would be much slower. // Each unrolled iteration processes 64 bytes. if (sizeof(nint) == sizeof(ulong)) { while (length >= (sizeof(ulong) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 0)); ulong value0 = Unsafe.ReadUnaligned(ref ri0); hash = unchecked(((hash << 5) + hash) ^ (int)value0 ^ (int)(value0 >> 32)); ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 1)); ulong value1 = Unsafe.ReadUnaligned(ref ri1); hash = unchecked(((hash << 5) + hash) ^ (int)value1 ^ (int)(value1 >> 32)); ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 2)); ulong value2 = Unsafe.ReadUnaligned(ref ri2); hash = unchecked(((hash << 5) + hash) ^ (int)value2 ^ (int)(value2 >> 32)); ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 3)); ulong value3 = Unsafe.ReadUnaligned(ref ri3); hash = unchecked(((hash << 5) + hash) ^ (int)value3 ^ (int)(value3 >> 32)); ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 4)); ulong value4 = Unsafe.ReadUnaligned(ref ri4); hash = unchecked(((hash << 5) + hash) ^ (int)value4 ^ (int)(value4 >> 32)); ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 5)); ulong value5 = Unsafe.ReadUnaligned(ref ri5); hash = unchecked(((hash << 5) + hash) ^ (int)value5 ^ (int)(value5 >> 32)); ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 6)); ulong value6 = Unsafe.ReadUnaligned(ref ri6); hash = unchecked(((hash << 5) + hash) ^ (int)value6 ^ (int)(value6 >> 32)); ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ulong) * 7)); ulong value7 = Unsafe.ReadUnaligned(ref ri7); hash = unchecked(((hash << 5) + hash) ^ (int)value7 ^ (int)(value7 >> 32)); length -= sizeof(ulong) << 3; offset += sizeof(ulong) << 3; } } // Each unrolled iteration processes 32 bytes while (length >= (sizeof(uint) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 0)); uint value0 = Unsafe.ReadUnaligned(ref ri0); hash = unchecked(((hash << 5) + hash) ^ (int)value0); ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 1)); uint value1 = Unsafe.ReadUnaligned(ref ri1); hash = unchecked(((hash << 5) + hash) ^ (int)value1); ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 2)); uint value2 = Unsafe.ReadUnaligned(ref ri2); hash = unchecked(((hash << 5) + hash) ^ (int)value2); ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 3)); uint value3 = Unsafe.ReadUnaligned(ref ri3); hash = unchecked(((hash << 5) + hash) ^ (int)value3); ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 4)); uint value4 = Unsafe.ReadUnaligned(ref ri4); hash = unchecked(((hash << 5) + hash) ^ (int)value4); ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 5)); uint value5 = Unsafe.ReadUnaligned(ref ri5); hash = unchecked(((hash << 5) + hash) ^ (int)value5); ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 6)); uint value6 = Unsafe.ReadUnaligned(ref ri6); hash = unchecked(((hash << 5) + hash) ^ (int)value6); ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(uint) * 7)); uint value7 = Unsafe.ReadUnaligned(ref ri7); hash = unchecked(((hash << 5) + hash) ^ (int)value7); length -= sizeof(uint) << 3; offset += sizeof(uint) << 3; } } // At this point (assuming AVX2), there will be up to 31 bytes // left, both for the vectorized and non vectorized paths. // That number would go up to 63 on AVX512 systems, in which case it is // still useful to perform this last loop unrolling. if (length >= (sizeof(ushort) << 3)) { ref byte ri0 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 0)); ushort value0 = Unsafe.ReadUnaligned(ref ri0); hash = unchecked(((hash << 5) + hash) ^ value0); ref byte ri1 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 1)); ushort value1 = Unsafe.ReadUnaligned(ref ri1); hash = unchecked(((hash << 5) + hash) ^ value1); ref byte ri2 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 2)); ushort value2 = Unsafe.ReadUnaligned(ref ri2); hash = unchecked(((hash << 5) + hash) ^ value2); ref byte ri3 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 3)); ushort value3 = Unsafe.ReadUnaligned(ref ri3); hash = unchecked(((hash << 5) + hash) ^ value3); ref byte ri4 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 4)); ushort value4 = Unsafe.ReadUnaligned(ref ri4); hash = unchecked(((hash << 5) + hash) ^ value4); ref byte ri5 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 5)); ushort value5 = Unsafe.ReadUnaligned(ref ri5); hash = unchecked(((hash << 5) + hash) ^ value5); ref byte ri6 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 6)); ushort value6 = Unsafe.ReadUnaligned(ref ri6); hash = unchecked(((hash << 5) + hash) ^ value6); ref byte ri7 = ref Unsafe.Add(ref r0, offset + (sizeof(ushort) * 7)); ushort value7 = Unsafe.ReadUnaligned(ref ri7); hash = unchecked(((hash << 5) + hash) ^ value7); length -= sizeof(ushort) << 3; offset += sizeof(ushort) << 3; } // Handle the leftover items while (length > 0) { hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset)); length -= 1; offset += 1; } return hash; } }