From eed917589a143e4d8da321c4f7a1a7a9a017c257 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Mon, 9 Sep 2024 16:00:01 +0200 Subject: [PATCH 01/15] vectorized --- src/ExceptionHelpers.cs | 2 +- src/Extensions/ReadOnlySpan/Linq/Max.cs | 1180 +++++++++++++++++ .../ReadOnlySpan_Max_Benchmark.cs | 58 + .../unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs | 20 +- 4 files changed, 1257 insertions(+), 3 deletions(-) create mode 100644 tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Max_Benchmark.cs diff --git a/src/ExceptionHelpers.cs b/src/ExceptionHelpers.cs index 79ef3e3..a484ee5 100644 --- a/src/ExceptionHelpers.cs +++ b/src/ExceptionHelpers.cs @@ -23,7 +23,7 @@ static ExceptionHelpers() combination |= flag; } - NegatedCombinationOfAllValidStringSplitOptions = (StringSplitOptions) ~combination; + NegatedCombinationOfAllValidStringSplitOptions = (StringSplitOptions)~combination; } internal static void ThrowIfGreaterThanOrEqual(T value, T other, diff --git a/src/Extensions/ReadOnlySpan/Linq/Max.cs b/src/Extensions/ReadOnlySpan/Linq/Max.cs index bf91b55..48f09ce 100644 --- a/src/Extensions/ReadOnlySpan/Linq/Max.cs +++ b/src/Extensions/ReadOnlySpan/Linq/Max.cs @@ -1,5 +1,12 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics; +#endif + namespace SpanExtensions { @@ -7,6 +14,151 @@ public static partial class ReadOnlySpanExtensions { #if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER + + /// + /// Returns the maximum value in . + /// + /// The type of elements in . + /// A to determine the maximum value of. + /// The maximum value in . + public static T Max(this ReadOnlySpan source) where T : IComparable + { + if(Vector512.IsHardwareAccelerated && Vector512.IsSupported && source.Length > Vector512.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector512.Count); + + Vector512 maxVector = Vector512.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector512.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector512.Max(maxVector, Vector512.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector512.Count); + } + + maxVector = Vector512.Max(maxVector, Vector512.LoadUnsafe(ref secondToLast)); + + T result = maxVector[0]; + for(int i = 1; i < Vector512.Count; i++) + { + T currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + T result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + T currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + T result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + T currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + T result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + T currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + T max = source[0]; + for(int i = 1; i < source.Length; i++) + { + T current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } +#else /// /// Returns the maximum value in . /// @@ -27,6 +179,1034 @@ public static T Max(this ReadOnlySpan source) where T : IComparable return max; } + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static byte Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + byte result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + byte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + byte result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + byte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + byte result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + byte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + byte max = source[0]; + for(int i = 1; i < source.Length; i++) + { + byte current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static ushort Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + ushort result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + ushort currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + ushort result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + ushort currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + ushort result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + ushort currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + ushort max = source[0]; + for(int i = 1; i < source.Length; i++) + { + ushort current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static uint Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + uint result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + uint currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + uint result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + uint currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + uint result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + uint currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + uint max = source[0]; + for(int i = 1; i < source.Length; i++) + { + uint current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static ulong Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref ulong current = ref MemoryMarshal.GetReference(source); + ref ulong secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + ulong result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + ulong currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref ulong current = ref MemoryMarshal.GetReference(source); + ref ulong secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + ulong result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + ulong currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + ulong max = source[0]; + for(int i = 1; i < source.Length; i++) + { + ulong current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static sbyte Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + sbyte result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + sbyte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + sbyte result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + sbyte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + sbyte result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + sbyte currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + sbyte max = source[0]; + for(int i = 1; i < source.Length; i++) + { + sbyte current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static short Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + short result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + short currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + short result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + short currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + short result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + short currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + short max = source[0]; + for(int i = 1; i < source.Length; i++) + { + short current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static int Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + int result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + int currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + int result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + int currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + int result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + int currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + int max = source[0]; + for(int i = 1; i < source.Length; i++) + { + int current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static long Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref long current = ref MemoryMarshal.GetReference(source); + ref long secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + long result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + long currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref long current = ref MemoryMarshal.GetReference(source); + ref long secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + long result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + long currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + long max = source[0]; + for(int i = 1; i < source.Length; i++) + { + long current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static float Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + float result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + float currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + float result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + float currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 maxVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + maxVector = Vector64.Max(maxVector, Vector64.LoadUnsafe(ref secondToLast)); + + float result = maxVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + float currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + float max = source[0]; + for(int i = 1; i < source.Length; i++) + { + float current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } + + /// + /// Returns the maximum value in . + /// + /// A to determine the maximum value of. + /// The maximum value in . + public static double Max(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref double current = ref MemoryMarshal.GetReference(source); + ref double secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 maxVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + maxVector = Vector256.Max(maxVector, Vector256.LoadUnsafe(ref secondToLast)); + + double result = maxVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + double currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref double current = ref MemoryMarshal.GetReference(source); + ref double secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 maxVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + maxVector = Vector128.Max(maxVector, Vector128.LoadUnsafe(ref secondToLast)); + + double result = maxVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + double currentResult = maxVector[i]; + + if(currentResult.CompareTo(result) > 0) + { + result = currentResult; + } + } + + return result; + } + + double max = source[0]; + for(int i = 1; i < source.Length; i++) + { + double current = source[i]; + if(current.CompareTo(max) > 0) + { + max = current; + } + } + return max; + } +#endif + /// /// Invokes a transform function on each element in and returns the maximum resulting value. /// diff --git a/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Max_Benchmark.cs b/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Max_Benchmark.cs new file mode 100644 index 0000000..996d26b --- /dev/null +++ b/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Max_Benchmark.cs @@ -0,0 +1,58 @@ +using BenchmarkDotNet.Attributes; + +namespace SpanExtensions.Tests.Performance +{ + [MemoryDiagnoser(false)] + public class ReadOnlySpan_Max_Benchmark + { + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Max(int[] value) + { + return value.AsSpan().Max(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Max_Array(int[] value) + { + return value.Max(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Max_StraightForward(int[] value) + { + ReadOnlySpan source = value; + int max = source[0]; + for(int i = 1; i < source.Length; i++) + { + int current = source[i]; + if(current > max) + { + max = current; + } + } + return max; + } + + public IEnumerable GetArgs() + { + Random random = new Random(3); + + int[] choices = Enumerable.Range(0, 10000).ToArray(); + + int[] data = random.GetItems(choices, 10); + + yield return data; + + data = random.GetItems(choices, 100); + + yield return data; + + data = random.GetItems(choices, 1000); + + yield return data; + } + } +} \ No newline at end of file diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs index 590bb64..3d57361 100644 --- a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs +++ b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs @@ -2,9 +2,25 @@ { public static partial class ReadOnlySpanLinqTests { - public sealed class Sum + [Theory] + [MemberData(nameof(GetMaxData))] + public static void Max(int[] source, int max) { - // TODO + Assert.Equal(max, source.AsSpan().Max()); + } + + public static TheoryData GetMaxData() + { + return new TheoryData() + { + { new int[] { 2935, 6975, 8649, 1984, 5604, 1805, 2501, 9472, 3486, 3799 }, 9472 }, + { new int[] { 7262, 8173, 7680, 5581, 2060, 5588, 9060, 4421, 9775, 2737, 2919, 4673, 6326, 4695, 9821, 303, 8623, 9953, 6771, 3145, 8169, 8480, 9919, 326, + 6999, 5262, 9340, 6876, 5468, 811, 1871, 4533, 2971, 9885, 6426, 7629, 303, 3810, 3431, 9574, 5051, 7159, 1189, 2734, 9070, 7947, 3371, 4572, 1468, + 2213, 4100, 7187, 6198, 4879, 1949, 8781, 8254, 7353, 8582, 6797, 6248, 2184, 8953, 8964, 865, 8385, 1701, 6441, 8268, 2191, 9999, 4813, 6522, 7197, + 8094, 1009, 7290, 7184, 1255, 9051, 1891, 4768, 5407, 3255, 6710, 4683, 8334, 8092, 7936, 7645, 9375, 2949, 5227, 6633, 3325, 2867, 8206, 2225, 1868, + 685 }, 9999 }, + { new int[] { 3832, 8712, 6609, 522, 3664, 6761, 424, 9539, 8435, 8549, 4490, 9323, 4448, 1112, 8819, 4020, 8048, 3353, 8666, 250, 7470, 5654, 1749, 4780, 3859, 285, 9523, 248, 4803, 7186, 7713, 2971, 1120, 1640, 869, 663, 8288, 1514, 5419, 9320, 8790, 6607, 1239, 655, 5977, 9592, 7187, 5668, 8306, 1465, 2233, 8736, 7071, 9140, 5965, 8178, 6963, 1828, 6662, 3378, 7237, 175, 4736, 1249, 835, 1518, 8202, 2807, 243, 8156, 5731, 6533, 7934, 9345, 1460, 862, 4414, 1093, 8802, 4267, 3098, 3855, 1941, 3338, 4952, 8977, 5900, 1979, 5675, 2691, 3700, 6459, 4852, 2040, 2082, 8614, 1870, 9990, 9820, 4459, 1389, 4379, 5425, 150, 5733, 5699, 801, 7725, 7680, 5102, 3763, 5869, 3025, 2395, 280, 3382, 8233, 1398, 6296, 1858, 5618, 6222, 7132, 7552, 4456, 9272, 1681, 5894, 7263, 2845, 8991, 4423, 1273, 4343, 2877, 8719, 8429, 1791, 7604, 9253, 8175, 8175, 4299, 572, 8927, 7830, 3433, 2456, 1760, 8699, 380, 472, 3694, 7961, 8841, 5166, 7246, 7873, 5694, 6460, 4017, 4907, 462, 4834, 6110, 9340, 4596, 8682, 9517, 1560, 4953, 6442, 3793, 7042, 3683, 7442, 1923, 6560, 8624, 6625, 5838, 9224, 4133, 8563, 2464, 8519, 729, 3311, 5502, 7710, 1472, 556, 6096, 1143, 5235, 3267, 7712, 9464, 4461, 9587, 3233, 4751, 2938, 199, 3746, 3938, 6678, 6651, 4278, 1398, 3243, 686, 9248, 9068, 622, 4792, 774, 1898, 2369, 7591, 8610, 1284, 3179, 1806, 88, 4396, 345, 2649, 1806, 415, 9730, 2458, 2098, 9037, 3392, 1087, 6285, 3934, 4816, 8525, 1841, 4078, 9033, 4104, 4467, 785, 1308, 7028, 521, 443, 2493, 5813, 7095, 6870, 977, 1949, 1572, 1132, 111, 9350, 3593, 4028, 4844, 3862, 1667, 784, 8588, 211, 5676, 9534, 8506, 6839, 7082, 3843, 5749, 4531, 2251, 9074, 7339, 9302, 3087, 3316, 2128, 1363, 7922, 3916, 5363, 5227, 8060, 1442, 9514, 5153, 3823, 5465, 4932, 7812, 9234, 5170, 2436, 3682, 2197, 1097, 1351, 986, 1936, 5653, 8731, 3251, 1120, 6445, 9697, 2498, 3792, 808, 6262, 276, 1900, 3480, 5940, 7751, 5421, 3361, 2150, 4233, 20, 3352, 3016, 1616, 8911, 7937, 5297, 7080, 6637, 3656, 7105, 1990, 1965, 1141, 9427, 2268, 5184, 2112, 4106, 1614, 1745, 7016, 1360, 3014, 9203, 4655, 5912, 5753, 9229, 4685, 8261, 8836, 8946, 7118, 966, 8583, 2637, 7115, 4339, 3183, 1147, 2616, 5860, 136, 3703, 4272, 8311, 758, 4052, 3672, 2567, 3309, 9254, 535, 2488, 3003, 1991, 1, 2413, 4256, 2024, 9543, 7850, 1952, 5395, 8268, 3044, 4847, 175, 843, 9631, 8069, 7773, 922, 467, 9128, 1156, 1224, 9311, 4931, 6343, 5154, 1701, 5557, 2117, 4952, 9581, 8410, 4629, 7963, 6591, 2635, 4701, 82, 1159, 1604, 4766, 3908, 4741, 5434, 1227, 3464, 582, 3208, 4040, 4497, 5535, 8331, 68, 3360, 1846, 767, 690, 7481, 7912, 6870, 7842, 2293, 9834, 443, 8686, 4633, 217, 2211, 4252, 6995, 3367, 7690, 9763, 8862, 4362, 7247, 6483, 3876, 3703, 2878, 4571, 8492, 1517, 7620, 9416, 1250, 8341, 1268, 6116, 5824, 1945, 7220, 2170, 4289, 3762, 2472, 4074, 4297, 6747, 6594, 3247, 8371, 8956, 7044, 1130, 7845, 8567, 1206, 8998, 4599, 4283, 6813, 3778, 5033, 2298, 9350, 776, 2214, 1026, 7436, 6292, 8948, 6095, 8428, 5050, 6147, 5520, 5474, 5100, 1889, 3173, 2185, 7128, 7108, 9631, 6200, 9535, 4472, 6490, 1570, 2682, 7135, 2270, 1517, 1540, 5131, 3441, 7136, 1990, 4411, 1696, 1859, 3270, 9310, 302, 4298, 2275, 528, 1994, 4982, 2325, 3093, 6105, 7109, 1425, 2098, 9684, 6669, 5402, 6098, 9814, 6303, 5724, 9456, 4754, 9156, 6678, 4578, 6888, 9918, 2706, 8384, 3483, 688, 192, 1313, 8914, 7817, 6806, 5333, 3924, 9007, 2477, 1507, 9245, 9588, 1029, 5161, 91, 9442, 5447, 6771, 1733, 5892, 4597, 5392, 6135, 3814, 4556, 1145, 7620, 7697, 3640, 2075, 2276, 3941, 9609, 5417, 6916, 111, 3183, 1866, 9863, 68, 2173, 806, 3825, 4216, 211, 5165, 8127, 1517, 4486, 7445, 4471, 5934, 6650, 7591, 6090, 4799, 5177, 5100, 3261, 5661, 7713, 6227, 5367, 402, 9230, 5303, 9978, 5612, 8244, 9980, 6258, 3580, 6908, 1664, 3719, 3791, 1567, 1919, 3603, 9390, 3017, 6102, 3211, 6194, 7604, 6342, 7290, 2018, 9326, 2117, 4933, 8083, 8605, 4201, 2355, 5945, 5438, 3423, 4986, 4907, 5186, 2515, 3272, 4506, 1187, 890, 9025, 4985, 3871, 2299, 3232, 3258, 1497, 3870, 2643, 1610, 3016, 9173, 2797, 2888, 8012, 7960, 6285, 6127, 5046, 8175, 4975, 2706, 9309, 7773, 8352, 8143, 6933, 8695, 4203, 502, 2829, 8705, 5006, 6713, 7316, 2305, 8147, 7026, 8884, 1675, 6586, 4734, 1558, 745, 2929, 6265, 626, 2097, 6894, 7226, 6229, 7145, 9459, 3011, 5915, 6318, 5675, 6097, 3947, 5088, 6325, 2801, 9667, 2140, 8780, 4311, 4166, 6083, 5571, 5707, 9813, 9259, 7242, 3370, 1589, 241, 1148, 8564, 4844, 2087, 7517, 4835, 1800, 6977, 4273, 5684, 9245, 1995, 798, 998, 6629, 2049, 3079, 3796, 5349, 3784, 5067, 9417, 1964, 8618, 2098, 4542, 6526, 1187, 7413, 6970, 9902, 6088, 1422, 5673, 5169, 7111, 1253, 1860, 7570, 1489, 1001, 2690, 7867, 3096, 5066, 2171, 5284, 4573, 9077, 7763, 6179, 3446, 8020, 7805, 5174, 1731, 6599, 6226, 9988, 2975, 8309, 612, 9563, 7303, 5781, 3156, 572, 5125, 5828, 9518, 796, 1219, 6225, 3860, 2782, 2376, 1550, 8868, 3552, 9927, 9257, 1952, 2110, 9649, 790, 6456, 8067, 3617, 499, 3438, 511, 5027, 1871, 4595, 3180, 388, 3126, 564, 7314, 1909, 1598, 159, 8745, 9559, 6967, 4960, 7221, 4160, 5022, 2797, 181, 7731, 2673, 61, 3717, 6356, 8502, 9913, 6512, 9324, 5088, 6955, 4625, 2389, 9006, 5768, 9347, 1629, 679, 2393, 9250, 986, 1554, 1642, 8328, 9098, 3207, 2550, 2681, 5829, 9235, 3907, 8595, 7702, 3257, 2780, 2353, 1809, 878, 6824, 1886, 3212, 4051, 7989, 6821, 4643, 5534, 6355, 552, 1199, 5613, 5591, 3481, 2628, 3547, 9194, 6177, 1031, 1733, 4619, 3149, 5951, 7232, 683, 89, 1181, 8360, 6923, 9131, 6226, 3415, 7537, 751, 3855, 507, 6037, 6934, 3564, 4820, 3685, 3563, 6851, 1997, 1482, 216, 3643, 425, 5966, 4154, 4062, 6602, 1321, 76, 6258, 3674, 5934, 5980, 3368, 7899, 5640, 6283, 8611, 7224, 4326, 7783, 8075, 4840, 9625, 2121, 7509, 2260, 2612, 6210, 8048, 1055, 6297, 3954, 5749, 466, 6445, 755, 2393, 2768, 5069, 9623, 2093, 7460, 4492, 180, 4572, 57, 3566, 5664, 9179, 7402, 4952, 9627, 7671, 3699, 2140, 8803, 800, 3845, 6645 }, 9990 } + }; } } } From f52af1e4d1be8b3fd771a07e1c4d1ea2fb9c595b Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Thu, 14 Nov 2024 10:48:54 +0100 Subject: [PATCH 02/15] refactored tests --- .../unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs index 3d57361..cdc8bfe 100644 --- a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs +++ b/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs @@ -1,26 +1,46 @@ namespace SpanExtensions.Tests.UnitTests { - public static partial class ReadOnlySpanLinqTests + public class LinqMaxTests { [Theory] [MemberData(nameof(GetMaxData))] - public static void Max(int[] source, int max) + public void Max(int[] source, int max) { - Assert.Equal(max, source.AsSpan().Max()); + ReadOnlySpan span = source.AsSpan(); + Assert.Equal(max, span.Max()); } public static TheoryData GetMaxData() { - return new TheoryData() + var data = new TheoryData(); + + var Samples10 = GetSampleSetInts(10); + var Samples100 = GetSampleSetInts(100); + var Samples1000 = GetSampleSetInts(1000); + + int max10 = Samples10.Max(); + int max100 = Samples100.Max(); + int max1000 = Samples1000.Max(); + + data.Add(Samples10, max10); + data.Add(Samples100, max100); + data.Add(Samples1000, max1000); + + return data; + } + + static int[] GetSampleSetInts(int count) + { + Random random = new Random(count); + + int[] sample = new int[count]; + + for(int i = 0; i < count; i++) { - { new int[] { 2935, 6975, 8649, 1984, 5604, 1805, 2501, 9472, 3486, 3799 }, 9472 }, - { new int[] { 7262, 8173, 7680, 5581, 2060, 5588, 9060, 4421, 9775, 2737, 2919, 4673, 6326, 4695, 9821, 303, 8623, 9953, 6771, 3145, 8169, 8480, 9919, 326, - 6999, 5262, 9340, 6876, 5468, 811, 1871, 4533, 2971, 9885, 6426, 7629, 303, 3810, 3431, 9574, 5051, 7159, 1189, 2734, 9070, 7947, 3371, 4572, 1468, - 2213, 4100, 7187, 6198, 4879, 1949, 8781, 8254, 7353, 8582, 6797, 6248, 2184, 8953, 8964, 865, 8385, 1701, 6441, 8268, 2191, 9999, 4813, 6522, 7197, - 8094, 1009, 7290, 7184, 1255, 9051, 1891, 4768, 5407, 3255, 6710, 4683, 8334, 8092, 7936, 7645, 9375, 2949, 5227, 6633, 3325, 2867, 8206, 2225, 1868, - 685 }, 9999 }, - { new int[] { 3832, 8712, 6609, 522, 3664, 6761, 424, 9539, 8435, 8549, 4490, 9323, 4448, 1112, 8819, 4020, 8048, 3353, 8666, 250, 7470, 5654, 1749, 4780, 3859, 285, 9523, 248, 4803, 7186, 7713, 2971, 1120, 1640, 869, 663, 8288, 1514, 5419, 9320, 8790, 6607, 1239, 655, 5977, 9592, 7187, 5668, 8306, 1465, 2233, 8736, 7071, 9140, 5965, 8178, 6963, 1828, 6662, 3378, 7237, 175, 4736, 1249, 835, 1518, 8202, 2807, 243, 8156, 5731, 6533, 7934, 9345, 1460, 862, 4414, 1093, 8802, 4267, 3098, 3855, 1941, 3338, 4952, 8977, 5900, 1979, 5675, 2691, 3700, 6459, 4852, 2040, 2082, 8614, 1870, 9990, 9820, 4459, 1389, 4379, 5425, 150, 5733, 5699, 801, 7725, 7680, 5102, 3763, 5869, 3025, 2395, 280, 3382, 8233, 1398, 6296, 1858, 5618, 6222, 7132, 7552, 4456, 9272, 1681, 5894, 7263, 2845, 8991, 4423, 1273, 4343, 2877, 8719, 8429, 1791, 7604, 9253, 8175, 8175, 4299, 572, 8927, 7830, 3433, 2456, 1760, 8699, 380, 472, 3694, 7961, 8841, 5166, 7246, 7873, 5694, 6460, 4017, 4907, 462, 4834, 6110, 9340, 4596, 8682, 9517, 1560, 4953, 6442, 3793, 7042, 3683, 7442, 1923, 6560, 8624, 6625, 5838, 9224, 4133, 8563, 2464, 8519, 729, 3311, 5502, 7710, 1472, 556, 6096, 1143, 5235, 3267, 7712, 9464, 4461, 9587, 3233, 4751, 2938, 199, 3746, 3938, 6678, 6651, 4278, 1398, 3243, 686, 9248, 9068, 622, 4792, 774, 1898, 2369, 7591, 8610, 1284, 3179, 1806, 88, 4396, 345, 2649, 1806, 415, 9730, 2458, 2098, 9037, 3392, 1087, 6285, 3934, 4816, 8525, 1841, 4078, 9033, 4104, 4467, 785, 1308, 7028, 521, 443, 2493, 5813, 7095, 6870, 977, 1949, 1572, 1132, 111, 9350, 3593, 4028, 4844, 3862, 1667, 784, 8588, 211, 5676, 9534, 8506, 6839, 7082, 3843, 5749, 4531, 2251, 9074, 7339, 9302, 3087, 3316, 2128, 1363, 7922, 3916, 5363, 5227, 8060, 1442, 9514, 5153, 3823, 5465, 4932, 7812, 9234, 5170, 2436, 3682, 2197, 1097, 1351, 986, 1936, 5653, 8731, 3251, 1120, 6445, 9697, 2498, 3792, 808, 6262, 276, 1900, 3480, 5940, 7751, 5421, 3361, 2150, 4233, 20, 3352, 3016, 1616, 8911, 7937, 5297, 7080, 6637, 3656, 7105, 1990, 1965, 1141, 9427, 2268, 5184, 2112, 4106, 1614, 1745, 7016, 1360, 3014, 9203, 4655, 5912, 5753, 9229, 4685, 8261, 8836, 8946, 7118, 966, 8583, 2637, 7115, 4339, 3183, 1147, 2616, 5860, 136, 3703, 4272, 8311, 758, 4052, 3672, 2567, 3309, 9254, 535, 2488, 3003, 1991, 1, 2413, 4256, 2024, 9543, 7850, 1952, 5395, 8268, 3044, 4847, 175, 843, 9631, 8069, 7773, 922, 467, 9128, 1156, 1224, 9311, 4931, 6343, 5154, 1701, 5557, 2117, 4952, 9581, 8410, 4629, 7963, 6591, 2635, 4701, 82, 1159, 1604, 4766, 3908, 4741, 5434, 1227, 3464, 582, 3208, 4040, 4497, 5535, 8331, 68, 3360, 1846, 767, 690, 7481, 7912, 6870, 7842, 2293, 9834, 443, 8686, 4633, 217, 2211, 4252, 6995, 3367, 7690, 9763, 8862, 4362, 7247, 6483, 3876, 3703, 2878, 4571, 8492, 1517, 7620, 9416, 1250, 8341, 1268, 6116, 5824, 1945, 7220, 2170, 4289, 3762, 2472, 4074, 4297, 6747, 6594, 3247, 8371, 8956, 7044, 1130, 7845, 8567, 1206, 8998, 4599, 4283, 6813, 3778, 5033, 2298, 9350, 776, 2214, 1026, 7436, 6292, 8948, 6095, 8428, 5050, 6147, 5520, 5474, 5100, 1889, 3173, 2185, 7128, 7108, 9631, 6200, 9535, 4472, 6490, 1570, 2682, 7135, 2270, 1517, 1540, 5131, 3441, 7136, 1990, 4411, 1696, 1859, 3270, 9310, 302, 4298, 2275, 528, 1994, 4982, 2325, 3093, 6105, 7109, 1425, 2098, 9684, 6669, 5402, 6098, 9814, 6303, 5724, 9456, 4754, 9156, 6678, 4578, 6888, 9918, 2706, 8384, 3483, 688, 192, 1313, 8914, 7817, 6806, 5333, 3924, 9007, 2477, 1507, 9245, 9588, 1029, 5161, 91, 9442, 5447, 6771, 1733, 5892, 4597, 5392, 6135, 3814, 4556, 1145, 7620, 7697, 3640, 2075, 2276, 3941, 9609, 5417, 6916, 111, 3183, 1866, 9863, 68, 2173, 806, 3825, 4216, 211, 5165, 8127, 1517, 4486, 7445, 4471, 5934, 6650, 7591, 6090, 4799, 5177, 5100, 3261, 5661, 7713, 6227, 5367, 402, 9230, 5303, 9978, 5612, 8244, 9980, 6258, 3580, 6908, 1664, 3719, 3791, 1567, 1919, 3603, 9390, 3017, 6102, 3211, 6194, 7604, 6342, 7290, 2018, 9326, 2117, 4933, 8083, 8605, 4201, 2355, 5945, 5438, 3423, 4986, 4907, 5186, 2515, 3272, 4506, 1187, 890, 9025, 4985, 3871, 2299, 3232, 3258, 1497, 3870, 2643, 1610, 3016, 9173, 2797, 2888, 8012, 7960, 6285, 6127, 5046, 8175, 4975, 2706, 9309, 7773, 8352, 8143, 6933, 8695, 4203, 502, 2829, 8705, 5006, 6713, 7316, 2305, 8147, 7026, 8884, 1675, 6586, 4734, 1558, 745, 2929, 6265, 626, 2097, 6894, 7226, 6229, 7145, 9459, 3011, 5915, 6318, 5675, 6097, 3947, 5088, 6325, 2801, 9667, 2140, 8780, 4311, 4166, 6083, 5571, 5707, 9813, 9259, 7242, 3370, 1589, 241, 1148, 8564, 4844, 2087, 7517, 4835, 1800, 6977, 4273, 5684, 9245, 1995, 798, 998, 6629, 2049, 3079, 3796, 5349, 3784, 5067, 9417, 1964, 8618, 2098, 4542, 6526, 1187, 7413, 6970, 9902, 6088, 1422, 5673, 5169, 7111, 1253, 1860, 7570, 1489, 1001, 2690, 7867, 3096, 5066, 2171, 5284, 4573, 9077, 7763, 6179, 3446, 8020, 7805, 5174, 1731, 6599, 6226, 9988, 2975, 8309, 612, 9563, 7303, 5781, 3156, 572, 5125, 5828, 9518, 796, 1219, 6225, 3860, 2782, 2376, 1550, 8868, 3552, 9927, 9257, 1952, 2110, 9649, 790, 6456, 8067, 3617, 499, 3438, 511, 5027, 1871, 4595, 3180, 388, 3126, 564, 7314, 1909, 1598, 159, 8745, 9559, 6967, 4960, 7221, 4160, 5022, 2797, 181, 7731, 2673, 61, 3717, 6356, 8502, 9913, 6512, 9324, 5088, 6955, 4625, 2389, 9006, 5768, 9347, 1629, 679, 2393, 9250, 986, 1554, 1642, 8328, 9098, 3207, 2550, 2681, 5829, 9235, 3907, 8595, 7702, 3257, 2780, 2353, 1809, 878, 6824, 1886, 3212, 4051, 7989, 6821, 4643, 5534, 6355, 552, 1199, 5613, 5591, 3481, 2628, 3547, 9194, 6177, 1031, 1733, 4619, 3149, 5951, 7232, 683, 89, 1181, 8360, 6923, 9131, 6226, 3415, 7537, 751, 3855, 507, 6037, 6934, 3564, 4820, 3685, 3563, 6851, 1997, 1482, 216, 3643, 425, 5966, 4154, 4062, 6602, 1321, 76, 6258, 3674, 5934, 5980, 3368, 7899, 5640, 6283, 8611, 7224, 4326, 7783, 8075, 4840, 9625, 2121, 7509, 2260, 2612, 6210, 8048, 1055, 6297, 3954, 5749, 466, 6445, 755, 2393, 2768, 5069, 9623, 2093, 7460, 4492, 180, 4572, 57, 3566, 5664, 9179, 7402, 4952, 9627, 7671, 3699, 2140, 8803, 800, 3845, 6645 }, 9990 } - }; + sample[i] = random.Next(); + } + + return sample; } } } From 85a87fbcb3dc1d6d2ce3a2957274fb330b9f1410 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Fri, 15 Nov 2024 11:30:21 +0100 Subject: [PATCH 03/15] updated vectorized Max() --- src/ExceptionHelpers.cs | 19 +++ src/Extensions/ReadOnlySpan/Linq/Max.cs | 172 ++++++++++++++++++------ 2 files changed, 149 insertions(+), 42 deletions(-) diff --git a/src/ExceptionHelpers.cs b/src/ExceptionHelpers.cs index a484ee5..fff3569 100644 --- a/src/ExceptionHelpers.cs +++ b/src/ExceptionHelpers.cs @@ -139,11 +139,24 @@ internal static void ThrowIfNegative(int value, } #endif + internal static void ThrowIfNull([NotNull] object? value, +#if NET8_0_OR_GREATER + [CallerArgumentExpression(nameof(value))] +#endif + string? paramName = null) + { + if(value is null) + { + ThrowNull(value, paramName); + } + } + [DoesNotReturn] static void ThrowGreaterThanOrEqual(T value, T other, string? paramName) { throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than '{other}'. (Parameter '{paramName}')"); } + [DoesNotReturn] static void ThrowLessThan(T value, T other, string? paramName) { @@ -155,4 +168,10 @@ static void ThrowNegative(T value, string? paramName) { throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be a non-negative value. (Parameter '{paramName}')"); } + + [DoesNotReturn] + static void ThrowNull(object? value, string? paramName) + { + throw new ArgumentNullException(paramName); + } } \ No newline at end of file diff --git a/src/Extensions/ReadOnlySpan/Linq/Max.cs b/src/Extensions/ReadOnlySpan/Linq/Max.cs index 48f09ce..85812b7 100644 --- a/src/Extensions/ReadOnlySpan/Linq/Max.cs +++ b/src/Extensions/ReadOnlySpan/Linq/Max.cs @@ -41,6 +41,7 @@ public static T Max(this ReadOnlySpan source) where T : IComparable maxVector = Vector512.Max(maxVector, Vector512.LoadUnsafe(ref secondToLast)); T result = maxVector[0]; + for(int i = 1; i < Vector512.Count; i++) { T currentResult = maxVector[i]; @@ -148,14 +149,17 @@ public static T Max(this ReadOnlySpan source) where T : IComparable } T max = source[0]; + for(int i = 1; i < source.Length; i++) { T current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } #else @@ -168,21 +172,24 @@ public static T Max(this ReadOnlySpan source) where T : IComparable public static T Max(this ReadOnlySpan source) where T : IComparable { T max = source[0]; + for(int i = 1; i < source.Length; i++) { T current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static byte Max(this ReadOnlySpan source) { @@ -280,21 +287,24 @@ public static byte Max(this ReadOnlySpan source) } byte max = source[0]; + for(int i = 1; i < source.Length; i++) { byte current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static ushort Max(this ReadOnlySpan source) { @@ -392,21 +402,24 @@ public static ushort Max(this ReadOnlySpan source) } ushort max = source[0]; + for(int i = 1; i < source.Length; i++) { ushort current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static uint Max(this ReadOnlySpan source) { @@ -504,21 +517,24 @@ public static uint Max(this ReadOnlySpan source) } uint max = source[0]; + for(int i = 1; i < source.Length; i++) { uint current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static ulong Max(this ReadOnlySpan source) { @@ -585,21 +601,24 @@ public static ulong Max(this ReadOnlySpan source) } ulong max = source[0]; + for(int i = 1; i < source.Length; i++) { ulong current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static sbyte Max(this ReadOnlySpan source) { @@ -697,21 +716,24 @@ public static sbyte Max(this ReadOnlySpan source) } sbyte max = source[0]; + for(int i = 1; i < source.Length; i++) { sbyte current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static short Max(this ReadOnlySpan source) { @@ -809,21 +831,24 @@ public static short Max(this ReadOnlySpan source) } short max = source[0]; + for(int i = 1; i < source.Length; i++) { short current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static int Max(this ReadOnlySpan source) { @@ -921,21 +946,24 @@ public static int Max(this ReadOnlySpan source) } int max = source[0]; + for(int i = 1; i < source.Length; i++) { int current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static long Max(this ReadOnlySpan source) { @@ -1002,21 +1030,24 @@ public static long Max(this ReadOnlySpan source) } long max = source[0]; + for(int i = 1; i < source.Length; i++) { long current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static float Max(this ReadOnlySpan source) { @@ -1114,21 +1145,24 @@ public static float Max(this ReadOnlySpan source) } float max = source[0]; + for(int i = 1; i < source.Length; i++) { float current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } /// /// Returns the maximum value in . /// - /// A to determine the maximum value of. + /// A to determine the maximum value of. /// The maximum value in . public static double Max(this ReadOnlySpan source) { @@ -1195,14 +1229,17 @@ public static double Max(this ReadOnlySpan source) } double max = source[0]; + for(int i = 1; i < source.Length; i++) { double current = source[i]; + if(current.CompareTo(max) > 0) { max = current; } } + return max; } #endif @@ -1222,15 +1259,18 @@ public static TResult Max(this ReadOnlySpan source, F TSource first = source[0]; TResult max = selector(first); + for(int i = 1; i < source.Length; i++) { TSource value = source[i]; TResult current = selector(value); + if(current.CompareTo(max) > 0) { max = current; } } + return max; } #else @@ -1294,14 +1334,17 @@ public static Half Max(this ReadOnlySpan source, Func selector) public static byte Max(this ReadOnlySpan source) { byte max = source[0]; + for(int i = 1; i < source.Length; i++) { byte current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1313,14 +1356,17 @@ public static byte Max(this ReadOnlySpan source) public static ushort Max(this ReadOnlySpan source) { ushort max = source[0]; + for(int i = 1; i < source.Length; i++) { ushort current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1332,14 +1378,17 @@ public static ushort Max(this ReadOnlySpan source) public static uint Max(this ReadOnlySpan source) { uint max = source[0]; + for(int i = 1; i < source.Length; i++) { uint current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1351,14 +1400,17 @@ public static uint Max(this ReadOnlySpan source) public static ulong Max(this ReadOnlySpan source) { ulong max = source[0]; + for(int i = 1; i < source.Length; i++) { ulong current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1370,14 +1422,17 @@ public static ulong Max(this ReadOnlySpan source) public static sbyte Max(this ReadOnlySpan source) { sbyte max = source[0]; + for(int i = 1; i < source.Length; i++) { sbyte current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1389,14 +1444,17 @@ public static sbyte Max(this ReadOnlySpan source) public static short Max(this ReadOnlySpan source) { short max = source[0]; + for(int i = 1; i < source.Length; i++) { short current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1408,14 +1466,17 @@ public static short Max(this ReadOnlySpan source) public static int Max(this ReadOnlySpan source) { int max = source[0]; + for(int i = 1; i < source.Length; i++) { int current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1427,14 +1488,17 @@ public static int Max(this ReadOnlySpan source) public static long Max(this ReadOnlySpan source) { long max = source[0]; + for(int i = 1; i < source.Length; i++) { long current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1446,14 +1510,17 @@ public static long Max(this ReadOnlySpan source) public static float Max(this ReadOnlySpan source) { float max = source[0]; + for(int i = 1; i < source.Length; i++) { float current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1465,14 +1532,17 @@ public static float Max(this ReadOnlySpan source) public static double Max(this ReadOnlySpan source) { double max = source[0]; + for(int i = 1; i < source.Length; i++) { double current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1484,14 +1554,17 @@ public static double Max(this ReadOnlySpan source) public static decimal Max(this ReadOnlySpan source) { decimal max = source[0]; + for(int i = 1; i < source.Length; i++) { decimal current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1503,14 +1576,17 @@ public static decimal Max(this ReadOnlySpan source) public static BigInteger Max(this ReadOnlySpan source) { BigInteger max = source[0]; + for(int i = 1; i < source.Length; i++) { BigInteger current = source[i]; + if(current > max) { max = current; } } + return max; } @@ -1531,15 +1607,18 @@ public static byte Max(this ReadOnlySpan source, Func selector) T first = source[0]; byte max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; byte current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1560,15 +1639,18 @@ public static ushort Max(this ReadOnlySpan source, Func selecto T first = source[0]; ushort max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; ushort current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1589,15 +1671,18 @@ public static uint Max(this ReadOnlySpan source, Func selector) T first = source[0]; uint max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; uint current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1618,15 +1703,18 @@ public static ulong Max(this ReadOnlySpan source, Func selector) T first = source[0]; ulong max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; ulong current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1640,22 +1728,22 @@ public static ulong Max(this ReadOnlySpan source, Func selector) /// is null. public static sbyte Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; sbyte max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; sbyte current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1669,22 +1757,22 @@ public static sbyte Max(this ReadOnlySpan source, Func selector) /// is null. public static short Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; short max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; short current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1698,22 +1786,22 @@ public static short Max(this ReadOnlySpan source, Func selector) /// is null. public static int Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; int max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; int current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1727,22 +1815,22 @@ public static int Max(this ReadOnlySpan source, Func selector) /// is null. public static long Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; long max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; long current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1756,22 +1844,22 @@ public static long Max(this ReadOnlySpan source, Func selector) /// is null. public static float Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; float max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; float current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1785,22 +1873,22 @@ public static float Max(this ReadOnlySpan source, Func selector) /// is null. public static double Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; double max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; double current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1814,22 +1902,22 @@ public static double Max(this ReadOnlySpan source, Func selecto /// is null. public static decimal Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; decimal max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; decimal current = selector(value); + if(current > max) { max = current; } } + return max; } @@ -1843,22 +1931,22 @@ public static decimal Max(this ReadOnlySpan source, Func selec /// is null. public static BigInteger Max(this ReadOnlySpan source, Func selector) { - if(selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } + ExceptionHelpers.ThrowIfNull(selector, nameof(selector)); T first = source[0]; BigInteger max = selector(first); + for(int i = 1; i < source.Length; i++) { T value = source[i]; BigInteger current = selector(value); + if(current > max) { max = current; } } + return max; } #endif From 7f7c33efaaf6c35e44aed06a7ec5b1165dfdaa62 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 13:05:09 +0100 Subject: [PATCH 04/15] fixed bug in range-based Split methods --- src/Enumerators/System/SpanSplitEnumerator.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Enumerators/System/SpanSplitEnumerator.cs b/src/Enumerators/System/SpanSplitEnumerator.cs index e07af58..ef4a1f6 100644 --- a/src/Enumerators/System/SpanSplitEnumerator.cs +++ b/src/Enumerators/System/SpanSplitEnumerator.cs @@ -23,27 +23,35 @@ public static partial class MemoryExtensions readonly SearchValues SearchValues = null!; #endif + int currentStartIndex; + int currentEndIndex; + int nextStartIndex; /// /// Gets the current element of the enumeration. /// /// Returns a instance that indicates the bounds of the current element withing the source span. - public Range Current { get; internal set; } + public readonly Range Current => new Range(currentStartIndex, currentEndIndex); internal SpanSplitEnumerator(ReadOnlySpan source, T delimiter) { Span = source; Delimiter = delimiter; - Current = new Range(0, 0); DelimiterSpan = default; mode = SpanSplitEnumeratorMode.Delimiter; + currentStartIndex = 0; + currentEndIndex = 0; + nextStartIndex = 0; } + internal SpanSplitEnumerator(ReadOnlySpan source, ReadOnlySpan delimiter, SpanSplitEnumeratorMode mode) { Span = source; DelimiterSpan = delimiter; - Current = new Range(0, 0); Delimiter = default!; this.mode = mode; + currentStartIndex = 0; + currentEndIndex = 0; + nextStartIndex = 0; } #if NET8_0 @@ -52,9 +60,11 @@ internal SpanSplitEnumerator(ReadOnlySpan source, SearchValues searchValue Span = source; Delimiter = default!; SearchValues = searchValues; - Current = new Range(0, 0); DelimiterSpan = default; mode = SpanSplitEnumeratorMode.Delimiter; + currentStartIndex = 0; + currentEndIndex = 0; + nextStartIndex = 0; } #endif /// @@ -106,15 +116,20 @@ public bool MoveNext() return false; } + currentStartIndex = nextStartIndex; + if(index < 0) { - Current = new Range(Span.Length, Span.Length); + currentEndIndex = Span.Length; + nextStartIndex = Span.Length; + mode = (SpanSplitEnumeratorMode)(-1); return true; } - Current = new Range(Current.End.Value + length, Current.Start.Value + index); - + currentEndIndex = currentStartIndex + index; + nextStartIndex = currentEndIndex + length; + return true; } } From 2e20462914e45eb2f4914e40b427e3b978500061 Mon Sep 17 00:00:00 2001 From: dragon <66683631+dragon7307@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:50:59 +0100 Subject: [PATCH 05/15] Update Changelog.md --- Changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Changelog.md b/Changelog.md index b82a780..a426767 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres not (yet) to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.1] - 2024-12-14 + +### Fixed + +- incorrect ranges returned by the range-based Split method for versions prior to .Net 9. + +### Changed + +- moved MemoryExtensions containing range-based Split method for versions prior to .Net 9 from `System` to `SpanExtensions`. +- grammatical issues in some documentation comments. + ## [1.5] - 2024-11-12 ### Added From cb81c0a9a369bb25c63bc422a39815fd89e2d80a Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 15:02:50 +0100 Subject: [PATCH 06/15] moved MemoryExtensions from 'System' to 'Spanextensions' and bumped version. --- src/Enumerators/System/SpanSplitEnumerator.cs | 6 +++--- src/SpanExtensions.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Enumerators/System/SpanSplitEnumerator.cs b/src/Enumerators/System/SpanSplitEnumerator.cs index ef4a1f6..539833c 100644 --- a/src/Enumerators/System/SpanSplitEnumerator.cs +++ b/src/Enumerators/System/SpanSplitEnumerator.cs @@ -1,10 +1,11 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; #if !NET9_0_OR_GREATER -namespace System +namespace SpanExtensions { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public static partial class MemoryExtensions @@ -144,5 +145,4 @@ internal enum SpanSplitEnumeratorMode } } } - #endif \ No newline at end of file diff --git a/src/SpanExtensions.csproj b/src/SpanExtensions.csproj index 2ada243..56ffae0 100644 --- a/src/SpanExtensions.csproj +++ b/src/SpanExtensions.csproj @@ -22,7 +22,7 @@ Span;Performance;Extension;String https://github.com/draconware-dev/SpanExtensions.Net/blob/main/Changelog.md LICENSE - 1.5 + 1.5.1 SpanExtensions.Net README.md icon.png From 98b6f3436c32f3f65759fba82fe757a1cf1a1454 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 15:04:16 +0100 Subject: [PATCH 07/15] fixed grammar --- src/Enumerators/System/SpanSplitEnumerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Enumerators/System/SpanSplitEnumerator.cs b/src/Enumerators/System/SpanSplitEnumerator.cs index 539833c..30a4a4f 100644 --- a/src/Enumerators/System/SpanSplitEnumerator.cs +++ b/src/Enumerators/System/SpanSplitEnumerator.cs @@ -30,7 +30,7 @@ public static partial class MemoryExtensions /// /// Gets the current element of the enumeration. /// - /// Returns a instance that indicates the bounds of the current element withing the source span. + /// Returns a instance that indicates the bounds of the current element within the source span. public readonly Range Current => new Range(currentStartIndex, currentEndIndex); internal SpanSplitEnumerator(ReadOnlySpan source, T delimiter) From 65230118721f3e0ff2b28ba109c3401543c22063 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 15:12:42 +0100 Subject: [PATCH 08/15] adapted namespaces for MemoryExtension Split methods also --- src/Extensions/ReadOnlySpan/Span/Split.cs | 5 +++-- src/Extensions/Span/Span/Split.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Extensions/ReadOnlySpan/Span/Split.cs b/src/Extensions/ReadOnlySpan/Span/Split.cs index 3807256..550804f 100644 --- a/src/Extensions/ReadOnlySpan/Span/Split.cs +++ b/src/Extensions/ReadOnlySpan/Span/Split.cs @@ -1,10 +1,11 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; #if !NET9_0_OR_GREATER -namespace System +namespace SpanExtensions { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public static partial class MemoryExtensions diff --git a/src/Extensions/Span/Span/Split.cs b/src/Extensions/Span/Span/Split.cs index 7ae44a8..f218aca 100644 --- a/src/Extensions/Span/Span/Split.cs +++ b/src/Extensions/Span/Span/Split.cs @@ -1,10 +1,11 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; #if !NET9_0_OR_GREATER -namespace System +namespace SpanExtensions { #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public static partial class MemoryExtensions From 683e782b2d3f47e9983519552e66660575f08e21 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 15:14:37 +0100 Subject: [PATCH 09/15] adjusted Project --- src/SpanExtensions.csproj | 108 +++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/SpanExtensions.csproj b/src/SpanExtensions.csproj index 56ffae0..ba1cf8e 100644 --- a/src/SpanExtensions.csproj +++ b/src/SpanExtensions.csproj @@ -1,66 +1,66 @@  - - net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.1 - disable - enable - True - AnyCPU - Span Extensions - dragon-cs - draconware - - ReadonlySpan<T> and Span<T> are great Types in C#, but unfortunately working with them can sometimes be sort of a hassle and some use cases seem straight up impossible, even though they are not. + + net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.1 + disable + enable + True + AnyCPU + Span Extensions + dragon-cs + draconware + + ReadonlySpan<T> and Span<T> are great Types in C#, but unfortunately working with them can sometimes be sort of a hassle and some use cases seem straight up impossible, even though they are not. - SpanExtensions.Net aims to help developers use ReadonlySpan<T> and Span<T> more productively, efficiently and safely and write overall more performant Programs. + SpanExtensions.Net aims to help developers use ReadonlySpan<T> and Span<T> more productively, efficiently and safely and write overall more performant Programs. - Never again switch back to using string instead of ReadonlySpan<T>, just because the method you seek is not supported. - - https://github.com/draconware-dev/SpanExtensions.Net - True - Copyright (c) 2024 draconware-dev - Span;Performance;Extension;String - https://github.com/draconware-dev/SpanExtensions.Net/blob/main/Changelog.md - LICENSE - 1.5.1 - SpanExtensions.Net - README.md - icon.png - + Never again switch back to using string instead of ReadonlySpan<T>, just because the method you seek is not supported. + + https://github.com/draconware-dev/SpanExtensions.Net + True + Copyright (c) 2024 draconware-dev + Span;Performance;Extension;String + https://github.com/draconware-dev/SpanExtensions.Net/blob/main/Changelog.md + LICENSE + 1.5.1 + SpanExtensions.Net + README.md + icon.png + - - portable - + + portable + - - portable - + + portable + - - portable - + + portable + - - portable - + + portable + - - - True - \ - - - True - \ - Always - - + + + True + \ + + + True + \ + Always + + - - - True - \ - - + + + True + \ + + From 45f514acbe74014c56a313f431a1ec6de3238151 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sat, 14 Dec 2024 16:10:52 +0100 Subject: [PATCH 10/15] fixed splitting mechanism Date: Wed, 17 Sep 2025 13:36:26 +0200 Subject: [PATCH 11/15] formatted ExceptionHelpers.cs --- src/ExceptionHelpers.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ExceptionHelpers.cs b/src/ExceptionHelpers.cs index a484ee5..a0c6298 100644 --- a/src/ExceptionHelpers.cs +++ b/src/ExceptionHelpers.cs @@ -37,6 +37,7 @@ internal static void ThrowIfGreaterThanOrEqual(T value, T other, ThrowGreaterThanOrEqual(value, other, paramName); } } + internal static void ThrowIfLessThan(T value, T other, #if NET8_0_OR_GREATER [CallerArgumentExpression(nameof(value))] @@ -144,6 +145,7 @@ static void ThrowGreaterThanOrEqual(T value, T other, string? paramName) { throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than '{other}'. (Parameter '{paramName}')"); } + [DoesNotReturn] static void ThrowLessThan(T value, T other, string? paramName) { From a67eaa7b35c9967c299ad9fc29663d7d6a26ac5e Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Wed, 17 Sep 2025 18:20:43 +0200 Subject: [PATCH 12/15] fixed bugs in Split --- src/Enumerators/System/SpanSplitEnumerator.cs | 10 +++---- src/ExceptionHelpers.cs | 1 - src/Extensions/ReadOnlySpan/Span/Split.cs | 29 ++++++++++++++++++- src/Extensions/Span/Linq/First.cs | 1 - src/Extensions/Span/Span/Split.cs | 10 +++---- src/SpanExtensions.csproj | 1 + .../ReadOnlySpan/Split/SplitAny/Tests.cs | 25 ++++++++++++++++ .../Split/SplitAny_StringSplitOptions/Data.cs | 4 +-- .../ToSystemEnumerableExtensions.cs | 17 +++++++++++ 9 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/Enumerators/System/SpanSplitEnumerator.cs b/src/Enumerators/System/SpanSplitEnumerator.cs index d4a4f68..a9ae76e 100644 --- a/src/Enumerators/System/SpanSplitEnumerator.cs +++ b/src/Enumerators/System/SpanSplitEnumerator.cs @@ -1,7 +1,5 @@ using System; using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; #if !NET9_0_OR_GREATER @@ -62,7 +60,7 @@ internal SpanSplitEnumerator(ReadOnlySpan source, SearchValues searchValue Delimiter = default!; SearchValues = searchValues; DelimiterSpan = default; - mode = SpanSplitEnumeratorMode.Delimiter; + mode = SpanSplitEnumeratorMode.SearchValues; currentStartIndex = 0; currentEndIndex = 0; nextStartIndex = 0; @@ -118,19 +116,19 @@ public bool MoveNext() } currentStartIndex = nextStartIndex; - + if(index < 0) { currentEndIndex = Span.Length; nextStartIndex = Span.Length; - + mode = (SpanSplitEnumeratorMode)(-1); return true; } currentEndIndex = currentStartIndex + index; nextStartIndex = currentEndIndex + length; - + return true; } } diff --git a/src/ExceptionHelpers.cs b/src/ExceptionHelpers.cs index a0c6298..2c1351f 100644 --- a/src/ExceptionHelpers.cs +++ b/src/ExceptionHelpers.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using SpanExtensions; diff --git a/src/Extensions/ReadOnlySpan/Span/Split.cs b/src/Extensions/ReadOnlySpan/Span/Split.cs index 550804f..5e0d835 100644 --- a/src/Extensions/ReadOnlySpan/Span/Split.cs +++ b/src/Extensions/ReadOnlySpan/Span/Split.cs @@ -1,16 +1,24 @@ using System; using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if !NET9_0_OR_GREATER namespace SpanExtensions { + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public static partial class MemoryExtensions #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member { + static readonly char[] WhiteSpaceDelimiters = new char[] { ' ', '\t', '\n', '\v', '\f', '\r', '\u0085', '\u00A0', '\u1680', + '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', + '\u200A', '\u2028', '\u2029', '\u202F', '\u205F', '\u3000' }; +#if NET8_0 + static readonly SearchValues WhiteSpaceSearchValues = SearchValues.Create(WhiteSpaceDelimiters); +#endif + /// /// Returns a type that allows for enumeration of each element within a split span /// using the provided separator character. @@ -47,6 +55,25 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan source, ReadO /// Returns a . public static SpanSplitEnumerator SplitAny(this ReadOnlySpan source, ReadOnlySpan separators) where T : IEquatable { + if(separators.Length == 0 && typeof(T) == typeof(char)) + { +#if NET8_0 + return new SpanSplitEnumerator(source, Unsafe.As>(WhiteSpaceSearchValues)); +#elif NET5_0_OR_GREATER + ref char data = ref MemoryMarshal.GetArrayDataReference(WhiteSpaceDelimiters); + ref T convertedData = ref Unsafe.As(ref data); + separators = MemoryMarshal.CreateReadOnlySpan(ref convertedData, WhiteSpaceDelimiters.Length); +#else + unsafe + { + fixed(char* ptr = &WhiteSpaceDelimiters[0]) + { + separators = new ReadOnlySpan(ptr, WhiteSpaceDelimiters.Length); + } + } +#endif + } + return new SpanSplitEnumerator(source, separators, SpanSplitEnumeratorMode.Any); } diff --git a/src/Extensions/Span/Linq/First.cs b/src/Extensions/Span/Linq/First.cs index f9caac5..2bdb1bf 100644 --- a/src/Extensions/Span/Linq/First.cs +++ b/src/Extensions/Span/Linq/First.cs @@ -1,5 +1,4 @@ using System; -using System.Reflection; namespace SpanExtensions { diff --git a/src/Extensions/Span/Span/Split.cs b/src/Extensions/Span/Span/Split.cs index f218aca..e2ced30 100644 --- a/src/Extensions/Span/Span/Split.cs +++ b/src/Extensions/Span/Span/Split.cs @@ -1,7 +1,5 @@ using System; using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; #if !NET9_0_OR_GREATER @@ -21,7 +19,7 @@ public static partial class MemoryExtensions /// Returns a . public static SpanSplitEnumerator Split(this Span source, T separator) where T : IEquatable { - return new SpanSplitEnumerator(source, separator); + return Split((ReadOnlySpan)source, separator); } /// @@ -34,7 +32,7 @@ public static SpanSplitEnumerator Split(this Span source, T separator) /// Returns a . public static SpanSplitEnumerator Split(this Span source, ReadOnlySpan separator) where T : IEquatable { - return new SpanSplitEnumerator(source, separator, SpanSplitEnumeratorMode.Sequence); + return Split((ReadOnlySpan)source, separator); } /// @@ -47,7 +45,7 @@ public static SpanSplitEnumerator Split(this Span source, ReadOnlySpan< /// Returns a . public static SpanSplitEnumerator SplitAny(this Span source, ReadOnlySpan separators) where T : IEquatable { - return new SpanSplitEnumerator(source, separators, SpanSplitEnumeratorMode.Any); + return SplitAny((ReadOnlySpan)source, separators); } #if NET8_0 @@ -65,7 +63,7 @@ public static SpanSplitEnumerator SplitAny(this Span source, ReadOnlySp /// public static SpanSplitEnumerator SplitAny(this Span source, SearchValues separators) where T : IEquatable { - return new SpanSplitEnumerator(source, separators); + return SplitAny((ReadOnlySpan)source, separators); } #endif diff --git a/src/SpanExtensions.csproj b/src/SpanExtensions.csproj index ba1cf8e..b44d033 100644 --- a/src/SpanExtensions.csproj +++ b/src/SpanExtensions.csproj @@ -26,6 +26,7 @@ SpanExtensions.Net README.md icon.png + true diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny/Tests.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny/Tests.cs index 4a14766..6eb2ea1 100644 --- a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny/Tests.cs +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny/Tests.cs @@ -148,6 +148,31 @@ public void UndefinedCountExceedingBehaviourOptionThrowsArgumentException() { Assert.Throws(() => ReadOnlySpanExtensions.SplitAny("aabb".AsSpan(), ['b', 'c'], 1, InvalidCountExceedingBehaviour)); } + + [Fact] + public void EmptyDelimitersUsesWhiteSpaceCharacters() + { + ReadOnlySpan source = "Hello World!\nThis is a test.\rLet's see how it works."; + ReadOnlySpan delimiters = ""; + var expected = new[] + { + "Hello".ToCharArray(), + "World!".ToCharArray(), + "This".ToCharArray(), + "is".ToCharArray(), + "a".ToCharArray(), + "test.".ToCharArray(), + "Let's".ToCharArray(), + "see".ToCharArray(), + "how".ToCharArray(), + "it".ToCharArray(), + "works.".ToCharArray() + }; + + var actual = source.SplitAny(delimiters).ToSystemEnumerable(source); + + Assert.Equal(expected, actual); + } } } } diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny_StringSplitOptions/Data.cs b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny_StringSplitOptions/Data.cs index 850a38b..56c6533 100644 --- a/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny_StringSplitOptions/Data.cs +++ b/tests/unit-tests/Tests/ReadOnlySpan/Split/SplitAny_StringSplitOptions/Data.cs @@ -1,6 +1,4 @@ -using static SpanExtensions.Tests.UnitTests.TestHelper; - -namespace SpanExtensions.Tests.UnitTests +namespace SpanExtensions.Tests.UnitTests { public static partial class ReadOnlySpanSplitTests { diff --git a/tests/unit-tests/ToSystemEnumerableExtensions.cs b/tests/unit-tests/ToSystemEnumerableExtensions.cs index 0297064..296389f 100644 --- a/tests/unit-tests/ToSystemEnumerableExtensions.cs +++ b/tests/unit-tests/ToSystemEnumerableExtensions.cs @@ -211,5 +211,22 @@ public static IEnumerable> ToSystemEnumerable(this SpanSplitSe return list; } + + public static IEnumerable> ToSystemEnumerable(this MemoryExtensions.SpanSplitEnumerator spanEnumerator, ReadOnlySpan span, int maxCount = 100) where T : IEquatable + { + List list = []; + + foreach(Range range in spanEnumerator) + { + list.Add(span[range].ToArray()); + + if(list.Count >= maxCount) + { + throw new IndexOutOfRangeException($"Enumeration exceeded {maxCount}."); + } + } + + return list; + } } } From 8275e7321aa41dd043b4b7577fbc2167f0e1a36b Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sun, 28 Sep 2025 16:44:45 +0200 Subject: [PATCH 13/15] vectorized Min() --- SpanExtensions.sln | 8 +- src/Extensions/ReadOnlySpan/Linq/Min.cs | 1217 +++++++++++++++++ .../ReadOnlySpan_Min_Benchmark.cs | 58 + .../Linq/{Sum.cs => LinqMinMaxTests.cs} | 29 +- 4 files changed, 1307 insertions(+), 5 deletions(-) create mode 100644 tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Min_Benchmark.cs rename tests/unit-tests/Tests/ReadOnlySpan/Linq/{Sum.cs => LinqMinMaxTests.cs} (58%) diff --git a/SpanExtensions.sln b/SpanExtensions.sln index b9f383d..3059f5c 100644 --- a/SpanExtensions.sln +++ b/SpanExtensions.sln @@ -15,16 +15,16 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Debug|Any CPU.Build.0 = Release|Any CPU {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Release|Any CPU.ActiveCfg = Release|Any CPU {75DE5AFD-663E-415D-9B95-6BC513BD4A07}.Release|Any CPU.Build.0 = Release|Any CPU {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Debug|Any CPU.Build.0 = Debug|Any CPU {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B48A0293-A7FF-4E39-8F8D-57B6F824D70F}.Release|Any CPU.Build.0 = Release|Any CPU - {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Debug|Any CPU.Build.0 = Release|Any CPU {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE74F420-1038-4E51-AC31-00DA964CC4F5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection diff --git a/src/Extensions/ReadOnlySpan/Linq/Min.cs b/src/Extensions/ReadOnlySpan/Linq/Min.cs index cffb0b5..2b6473b 100644 --- a/src/Extensions/ReadOnlySpan/Linq/Min.cs +++ b/src/Extensions/ReadOnlySpan/Linq/Min.cs @@ -1,5 +1,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.Intrinsics; +#endif namespace SpanExtensions { @@ -7,6 +13,8 @@ public static partial class ReadOnlySpanExtensions { #if NET7_0_OR_GREATER +#if NET8_0_OR_GREATER + /// /// Returns the minimum value in . /// @@ -15,18 +23,1227 @@ public static partial class ReadOnlySpanExtensions /// The minimum value in . public static T Min(this ReadOnlySpan source) where T : IComparable { + if(Vector512.IsHardwareAccelerated && Vector512.IsSupported && source.Length > Vector512.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector512.Count); + + Vector512 minVector = Vector512.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector512.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector512.Min(minVector, Vector512.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector512.Count); + } + + minVector = Vector512.Min(minVector, Vector512.LoadUnsafe(ref secondToLast)); + + T result = minVector[0]; + + for(int i = 1; i < Vector512.Count; i++) + { + T currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + T result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + T currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + T result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + T currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref T current = ref MemoryMarshal.GetReference(source); + ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + T result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + T currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + T min = source[0]; + for(int i = 1; i < source.Length; i++) { T current = source[i]; + if(current.CompareTo(min) < 0) { min = current; } } + + return min; + } +#else + + /// + /// Returns the minimum value in . + /// + /// The type of elements in . + /// A to determine the minimum value of. + /// The minimum value in . + public static T Min(this ReadOnlySpan source) where T : IComparable + { + T min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + T current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static byte Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + byte result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + byte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + byte result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + byte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref byte current = ref MemoryMarshal.GetReference(source); + ref byte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + byte result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + byte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + byte min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + byte current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static ushort Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + ushort result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + ushort currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + ushort result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + ushort currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref ushort current = ref MemoryMarshal.GetReference(source); + ref ushort secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + ushort result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + ushort currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + ushort min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + ushort current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + return min; } + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static uint Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + uint result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + uint currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + uint result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + uint currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref uint current = ref MemoryMarshal.GetReference(source); + ref uint secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + uint result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + uint currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + uint min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + uint current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static ulong Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref ulong current = ref MemoryMarshal.GetReference(source); + ref ulong secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + ulong result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + ulong currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref ulong current = ref MemoryMarshal.GetReference(source); + ref ulong secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + ulong result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + ulong currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + ulong min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + ulong current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static sbyte Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + sbyte result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + sbyte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + sbyte result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + sbyte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref sbyte current = ref MemoryMarshal.GetReference(source); + ref sbyte secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + sbyte result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + sbyte currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + sbyte min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + sbyte current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static short Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + short result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + short currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + short result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + short currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref short current = ref MemoryMarshal.GetReference(source); + ref short secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + short result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + short currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + short min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + short current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static int Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + int result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + int currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + int result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + int currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref int current = ref MemoryMarshal.GetReference(source); + ref int secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + int result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + int currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + int min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + int current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static long Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref long current = ref MemoryMarshal.GetReference(source); + ref long secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + long result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + long currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref long current = ref MemoryMarshal.GetReference(source); + ref long secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + long result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + long currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + long min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + long current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static float Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + float result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + float currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + float result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + float currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + { + ref float current = ref MemoryMarshal.GetReference(source); + ref float secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); + + Vector64 minVector = Vector64.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector64.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector64.Count); + } + + minVector = Vector64.Min(minVector, Vector64.LoadUnsafe(ref secondToLast)); + + float result = minVector[0]; + + for(int i = 1; i < Vector64.Count; i++) + { + float currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + float min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + float current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } + + /// + /// Returns the minimum value in . + /// + /// A to determine the minimum value of. + /// The minimum value in . + public static double Min(this ReadOnlySpan source) + { + if(Vector256.IsHardwareAccelerated && Vector256.IsSupported && source.Length > Vector256.Count) + { + ref double current = ref MemoryMarshal.GetReference(source); + ref double secondToLast = ref Unsafe.Add(ref current, source.Length - Vector256.Count); + + Vector256 minVector = Vector256.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector256.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + + minVector = Vector256.Min(minVector, Vector256.LoadUnsafe(ref secondToLast)); + + double result = minVector[0]; + + for(int i = 1; i < Vector256.Count; i++) + { + double currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + { + ref double current = ref MemoryMarshal.GetReference(source); + ref double secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); + + Vector128 minVector = Vector128.LoadUnsafe(ref current); + current = ref Unsafe.Add(ref current, Vector128.Count); + + while(Unsafe.IsAddressLessThan(ref current, ref secondToLast)) + { + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref current)); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + + minVector = Vector128.Min(minVector, Vector128.LoadUnsafe(ref secondToLast)); + + double result = minVector[0]; + + for(int i = 1; i < Vector128.Count; i++) + { + double currentResult = minVector[i]; + + if(currentResult.CompareTo(result) < 0) + { + result = currentResult; + } + } + + return result; + } + + double min = source[0]; + + for(int i = 1; i < source.Length; i++) + { + double current = source[i]; + + if(current.CompareTo(min) > 0) + { + min = current; + } + } + + return min; + } +#endif + /// /// Invokes a transform function on each element in and returns the minimum resulting value. /// diff --git a/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Min_Benchmark.cs b/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Min_Benchmark.cs new file mode 100644 index 0000000..41b4be9 --- /dev/null +++ b/tests/Performance/Tests/ReadOnlySpan/ReadOnlySpan_Min_Benchmark.cs @@ -0,0 +1,58 @@ +using BenchmarkDotNet.Attributes; + +namespace SpanExtensions.Tests.Performance +{ + [MemoryDiagnoser(false)] + public class ReadOnlySpan_Min_Benchmark + { + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Min(int[] value) + { + return value.AsSpan().Min(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Min_Array(int[] value) + { + return value.Min(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetArgs))] + public int Min_StraightForward(int[] value) + { + ReadOnlySpan source = value; + int min = source[0]; + for(int i = 1; i < source.Length; i++) + { + int current = source[i]; + if(current < min) + { + min = current; + } + } + return min; + } + + public IEnumerable GetArgs() + { + Random random = new Random(3); + + int[] choices = Enumerable.Range(0, 10000).ToArray(); + + int[] data = random.GetItems(choices, 10); + + yield return data; + + data = random.GetItems(choices, 100); + + yield return data; + + data = random.GetItems(choices, 1000); + + yield return data; + } + } +} \ No newline at end of file diff --git a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs b/tests/unit-tests/Tests/ReadOnlySpan/Linq/LinqMinMaxTests.cs similarity index 58% rename from tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs rename to tests/unit-tests/Tests/ReadOnlySpan/Linq/LinqMinMaxTests.cs index cdc8bfe..61b7104 100644 --- a/tests/unit-tests/Tests/ReadOnlySpan/Linq/Sum.cs +++ b/tests/unit-tests/Tests/ReadOnlySpan/Linq/LinqMinMaxTests.cs @@ -1,6 +1,6 @@ namespace SpanExtensions.Tests.UnitTests { - public class LinqMaxTests + public class LinqMinMaxTests { [Theory] [MemberData(nameof(GetMaxData))] @@ -10,6 +10,14 @@ public void Max(int[] source, int max) Assert.Equal(max, span.Max()); } + [Theory] + [MemberData(nameof(GetMinData))] + public void Min(int[] source, int min) + { + ReadOnlySpan span = source.AsSpan(); + Assert.Equal(min, span.Min()); + } + public static TheoryData GetMaxData() { var data = new TheoryData(); @@ -29,6 +37,25 @@ public static TheoryData GetMaxData() return data; } + public static TheoryData GetMinData() + { + var data = new TheoryData(); + + var Samples10 = GetSampleSetInts(10); + var Samples100 = GetSampleSetInts(100); + var Samples1000 = GetSampleSetInts(1000); + + int min10 = Samples10.Min(); + int min100 = Samples100.Min(); + int min1000 = Samples1000.Min(); + + data.Add(Samples10, min10); + data.Add(Samples100, min100); + data.Add(Samples1000, min1000); + + return data; + } + static int[] GetSampleSetInts(int count) { Random random = new Random(count); From e152549e34064067786bfac09bb41ceeae1cb9cf Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sun, 28 Sep 2025 20:58:02 +0200 Subject: [PATCH 14/15] prepared for version 2 --- .github/CONTRIBUTING.md | 22 +++++------ .github/README.md | 75 ++----------------------------------- .github/SECURITY.md | 2 +- Changelog.md | 60 +++++++++++++++++------------ LICENSE | 2 +- src/README.md | 79 +++------------------------------------ src/SpanExtensions.csproj | 12 +++--- 7 files changed, 63 insertions(+), 189 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 503d190..62da409 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,28 +1,28 @@ # Contributing to SpanExtensions.Net - + ### Did you catch a bug? **!!!Important!!!** -If the bug poses a threat to the security of dependent applications, please refer to our [Security Policy](SECURITY.md). +If the bug poses a threat to the security of dependent applications, please refer to our [Security Policy](SECURITY.md). There are two options available: -1. You can open an issue describing exactly: - - What is the bug and what did you expect to happen? - - How may it be reproduced (It is often helpful to include images or alike). - - Include the Version of SpanExtensions.Net affected. -2. Even better, you can fix the issue yourself by submitting a pull reqest. +1. You can open an issue describing exactly: + - What the bug is and what you expected to happen? + - How it may be reproduced (It is often helpful to include images or alike). + - hich versions of SpanExtensions.Net affected. +2. Even better, fix any issue yourself by submitting a pull request. ### Do you have a feature request? Tell us all about it (here)[]. Please consider however, that features should generally be implementable via Extension Methods, otherwise they are unlikely to be accepted. -### Did you notice a mistake or an error in the documentation? +### Did you notice a mistake or an error in the documentation? -If you found a spelling mistake or an ambigous or unclear sentence or phrase in the XML documentation in C# source files or in any of the documentation for this repository (for example: Readme.md), do one of the following: +If you found a spelling mistake or an ambigous or unclear sentence or phrase in the XML documentation in C# source files or in any of the documentation for this repository (for example: Readme.md), do one of the following: -[Open an **Issue**](https://github.com/draconware-dev/SpanExtensions.Net/issues/new/choose) with the tag `documentation`. It is recommended to follow the [example template](https://github.com/draconware-dev/SpanExtensions.Net/issues/new?assignees=&labels=documentation&projects=&template=documentation-report.md&title=Documentation+Error). +[Open an **Issue**](https://github.com/draconware-dev/SpanExtensions.Net/issues/new/choose) with the tag `documentation`. It is recommended to follow the [example template](https://github.com/draconware-dev/SpanExtensions.Net/issues/new?assignees=&labels=documentation&projects=&template=documentation-report.md&title=Documentation+Error). **OR** -[Open a **Pull Request**](https://github.com/draconware-dev/SpanExtensions.Net/compare) linked to an **Issue** tagged `documentation`, as grammatical errors are usually easier to fix, than they are to describe. In this case it is not necessary to describe the error in detail, a brief report with reasoning should do, unless it is not evident from the Pull Request, what actually was changed or why it was changed. +[Open a **Pull Request**](https://github.com/draconware-dev/SpanExtensions.Net/compare) linked to an **Issue** tagged `documentation`, as grammatical errors are usually easier to fix, than they are to describe. In this case it is not necessary to describe the error in detail, a brief report with reasoning should do, unless it is not evident from the Pull Request, what actually was changed or why it was changed. diff --git a/.github/README.md b/.github/README.md index 014f0cb..ca4ae12 100644 --- a/.github/README.md +++ b/.github/README.md @@ -5,84 +5,17 @@ [![GitHub License](https://img.shields.io/github/license/draconware-dev/SpanExtensions.Net)](https://github.com/draconware-dev/SpanExtensions.Net/blob/main/LICENSE) ## About -**`ReadonlySpan`** and **`Span`** are great Types in _C#_, but unfortunately working with them can sometimes be sort of a hassle and some use cases seem straight up impossible, even though they are not. -**SpanExtensions.Net** aims to help developers use `ReadonlySpan` and `Span` more **productively**, **efficiently** and **safely** and write overall more **performant** Programs. - -Never again switch back to using `string` instead of `ReadonlySpan`, just because the method you seek is not supported. - -**SpanExtensions.Net** provides alternatives for many missing Extension Methods for **`ReadonlySpan`** and **`Span`**, ranging from `string.Split()` over `Enumerable.Skip()` and `Enumerable.Take()` to an improved `ReadOnlySpan.IndexOf()`. +Never again switch back to using `string` instead of `ReadonlySpan`, just because the method you seek is not supported. -## Methods -The following **Extension Methods** are contained: - -#### String Methods made available for **`ReadonlySpan`** and **`Span`**: - -- `(ReadOnly-)Span.Split(T delimiter)` -- `(ReadOnly-)Span.Split(T delimiter, int count)` -- `(ReadOnly-)Span.Split(T delimiter, StringSplitOptions options)` -- `(ReadOnly-)Span.Split(T delimiter, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, int count)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, StringSplitOptions options)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, int count)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, StringSplitOptions options)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.Remove(int startIndex)` -- `Span.Replace(T oldT, T newT)` - -#### Linq Methods made available for **`ReadonlySpan`** and **`Span`**: - -- `(ReadOnly-)Span.All(Predicate predicate)` -- `(ReadOnly-)Span.Any(Predicate predicate)` -- `(ReadOnly-)Span.Average()` -- `(ReadOnly-)Span.Sum()` -- `(ReadOnly-)Span.Skip(int count)` -- `(ReadOnly-)Span.Take(int count)` -- `(ReadOnly-)Span.SkipLast(int count)` -- `(ReadOnly-)Span.Takelast(int count)` -- `(ReadOnly-)Span.SkipWhile(Predicate condition)` -- `(ReadOnly-)Span.TakeWhile(Predicate condition)` -- `(Readonly-)Span.First()` -- `(Readonly-)Span.First(Predicate predicate)` -- `(Readonly-)Span.FirstOrDefault()` -- `(Readonly-)Span.FirstOrDefault(Predicate predicate)` -- `(Readonly-)Span.FirstOrDefault(T defaultValue)` -- `(Readonly-)Span.FirstOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.Last()` -- `(Readonly-)Span.Last(Predicate predicate)` -- `(Readonly-)Span.LastOrDefault()` -- `(Readonly-)Span.LastOrDefault(Predicate predicate)` -- `(Readonly-)Span.LastOrDefault(T defaultValue)` -- `(Readonly-)Span.LastOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.Single()` -- `(Readonly-)Span.Single(Predicate predicate)` -- `(Readonly-)Span.SingleOrDefault()` -- `(Readonly-)Span.SingleOrDefault(Predicate predicate)` -- `(Readonly-)Span.SingleOrDefault(T defaultValue)` -- `(Readonly-)Span.SingleOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.ElementAt(int index)` -- `(Readonly-)Span.ElementAt(Index index)` -- `(Readonly-)Span.ElementAtOrDefault(int index)` -- `(Readonly-)Span.ElementAtOrDefault(Index index)` -- `(Readonly-)Span.ElementAtOrDefault(int index, T defaultValue)` -- `(Readonly-)Span.ElementAtOrDefault(Index index, T defaultValue)` -- `(Readonly-)Span.Min()` -- `(Readonly-)Span.Min(Func selector)` -- `(Readonly-)Span.MinBy(Func keySelector)` -- `(Readonly-)Span.MinBy(Func keySelector, IComparer comparer)` -- `(Readonly-)Span.Max()` -- `(Readonly-)Span.Max(Func selector)` -- `(Readonly-)Span.MaxBy(Func keySelector)` -- `(Readonly-)Span.MaxBy(Func keySelector, IComparer comparer)` +**SpanExtensions.Net** aims to help developers use `ReadonlySpan` and `Span` more **productively**, **efficiently** and **safely** and write overall more **performant** Programs. ## Contributing Thank you for your interest in contributing to this project - Please see [Contributing](CONTRIBUTING.md)! + ## License -Copyright (c) draconware-dev. All rights reserved. +Copyright (c) draconware-dev. All rights reserved. Licensed under the [MIT](../LICENSE) License. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 9cf7c9c..d960c75 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -If you find a security vulnerability please message **draconware@gmail.com** about the vulnerability and do not, **under no circumstances**, open a public issue. +If you find a security vulnerability please message **draconware@gmail.com** about the vulnerability and do not, **under no circumstances**, open a public issue. diff --git a/Changelog.md b/Changelog.md index a426767..351bd16 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,7 +3,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres not (yet) to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from v2.0 onwards. + +## [2.0.0] - 2025-9-28 + +### Fixed + +- an empty array not being treated as whitespace by range-based Split methods (https://github.com/draconware-dev/SpanExtensions.Net/pull/27) +- the SearchValues-overload of range-based Split methods. + +### Changed + +- vectorized `Min()` and `Max()`. +- transitioned to semantic versioning. ## [1.5.1] - 2024-12-14 @@ -11,7 +23,7 @@ and this project adheres not (yet) to [Semantic Versioning](https://semver.org/s - incorrect ranges returned by the range-based Split method for versions prior to .Net 9. -### Changed +### Changed - moved MemoryExtensions containing range-based Split method for versions prior to .Net 9 from `System` to `SpanExtensions`. - grammatical issues in some documentation comments. @@ -22,49 +34,49 @@ and this project adheres not (yet) to [Semantic Versioning](https://semver.org/s - implementations of the newly introduced Span.Split methods from .Net 9 for any version prior to .Net 9 to maintain backwards-compatibility across .Net versions. -### Changed +### Changed - Split extension methods to refer to new split implementations compatible to the ones in .Net 9 and made .Net 9 split methods the default from that version onwards. The original split methods are still accessible as static methods. - original Split methods are no longer available without passing span as a parameter. ## [1.4.2] - 2024-10-29 -### Added +### Added - `(Readonly-)Span.Count(...)` overloads to all versions before .Net 8 matching these introduced in .Net 8. -### Changed +### Changed -- blocked compilation on .Net 9 due to known incompatibilities, which are to be resolved in version 1.5. +- blocked compilation on .Net 9 due to known incompatibilities, which are to be resolved in version 1.5. ## [1.4.1] - 2024-9-9 -### Fixed +### Fixed - a collision between the `Span.Replace` method provided by SpanExtensions and the one newly provided by .Net 8. ## [1.4] - 2024-9-3 -### Added +### Added - `(Readonly-)Span.Count()` - `(Readonly-)Span.Count(Predicate predicate)` -### Changed +### Changed - `Split` to throw an `ArgumentException` instead of an `InvalidCountExceedingBehaviourException` -### Removed +### Removed - `InvalidCountExceedingBehaviourException` -### Fixed +### Fixed -- various issues with `Split` (https://github.com/draconware-dev/SpanExtensions.Net/pull/11) +- various issues with `Split` (https://github.com/draconware-dev/SpanExtensions.Net/pull/11) ## [1.3] - 2024-3-19 -### Added +### Added - Compatibility with **.Net 8** - `(Readonly-)Span.First()` @@ -102,42 +114,42 @@ and this project adheres not (yet) to [Semantic Versioning](https://semver.org/s - nuget badge to README (https://github.com/draconware-dev/SpanExtensions.Net/pull/12) - `CountExceedingBehaviour`, which is passed to Split, defining how to properly handle its remaining elements. -### Changed +### Changed - documentation comments to better reflect the dotnet style (https://github.com/draconware-dev/SpanExtensions.Net/pull/8) - swapped order of `count` and `stringSplitOptions arguments` in `Split` methods. - renamed argument `span` in `Split` methods to `source`. -### Fixed +### Fixed - empty spans being ignored if they are the last element to be returned from `Split` and are therefore not returned. (https://github.com/draconware-dev/SpanExtensions.Net/pull/10) ## [1.2.1] - 2024-1-25 -### Fixed +### Fixed - Ambiguous Extension Methods (https://github.com/draconware-dev/SpanExtensions.Net/issues/6) -- Correctness of some documentation comments +- Correctness of some documentation comments ## [1.2.0] - 2023-12-28 -### Added +### Added - Missing documentation comments -### Fixed +### Fixed - Grammatical issues in some documentation comments -### Changed +### Changed -- moved custom Enumerators into `SpanExtensions.Enumerators` +- moved custom Enumerators into `SpanExtensions.Enumerators` - declared every `GetEnumerator` method in a ref struct as `readonly` -- renamed the source ReadOnlySpan in 10 out of 12 custom Enumerators from *span* to *source* +- renamed the source ReadOnlySpan in 10 out of 12 custom Enumerators from _span_ to _source_ ## [1.1.0] - 2023-11-4 -### Added +### Added - Compatibility with **.Net 6** - Compatibility with **.Net 5** @@ -149,7 +161,7 @@ and this project adheres not (yet) to [Semantic Versioning](https://semver.org/s - Missing documentation comments - Changelog -### Fixed +### Fixed - Grammatical issues in some documentation comments - Broken link to the repository on nuget ([#3](https://github.com/draconware-dev/SpanExtensions.Net/pull/3)) diff --git a/LICENSE b/LICENSE index 128eeaf..81f048e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 draconware-dev +Copyright (c) 2025 draconware-dev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/README.md b/src/README.md index 09c45ba..6797ee8 100644 --- a/src/README.md +++ b/src/README.md @@ -1,86 +1,17 @@ # SpanExtensions ## About -**`ReadonlySpan`** and **`Span`** are great Types in _C#_, but unfortunately working with them can sometimes be sort of a hassle and some use cases seem straight up impossible, even though they are not. - -**SpanExtensions.Net** aims to help developers use `ReadonlySpan` and `Span` more **productively**, **efficiently** and **safely** and write overall more **performant** Programs. - -Never again switch back to using `string` instead of `ReadonlySpan`, just because the method you seek is not supported. - -**SpanExtensions.Net** provides alternatives for many missing Extension Methods for **`ReadonlySpan`** and **`Span`**, ranging from `string.Split()` over `Enumerable.Skip()` and `Enumerable.Take()` to an improved `ReadOnlySpan.IndexOf()`. - -## Methods -The following **Extension Methods** are contained: - -#### String Methods made available for **`ReadonlySpan`** and **`Span`**: - -- `(ReadOnly-)Span.Split(T delimiter)` -- `(ReadOnly-)Span.Split(T delimiter, int count)` -- `(ReadOnly-)Span.Split(T delimiter, StringSplitOptions options)` -- `(ReadOnly-)Span.Split(T delimiter, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, int count)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, StringSplitOptions options)` -- `(ReadOnly-)Span.Split(ReadOnlySpan delimiters, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, int count)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, StringSplitOptions options)` -- `(ReadOnly-)Span.SplitAny(ReadOnlySpan delimiters, StringSplitOptions options, int count)` -- `(ReadOnly-)Span.Remove(int startIndex)` -- `Span.Replace(T oldT, T newT)` -#### Linq Methods made available for **`ReadonlySpan`** and **`Span`**: +Never again switch back to using `string` instead of `ReadonlySpan`, just because the method you seek is not supported. -- `(ReadOnly-)Span.All(Predicate predicate)` -- `(ReadOnly-)Span.Any(Predicate predicate)` -- `(ReadOnly-)Span.Average()` -- `(ReadOnly-)Span.Sum()` -- `(ReadOnly-)Span.Min()` -- `(ReadOnly-)Span.Max()` -- `(ReadOnly-)Span.Skip(int count)` -- `(ReadOnly-)Span.Take(int count)` -- `(ReadOnly-)Span.SkipLast(int count)` -- `(ReadOnly-)Span.Takelast(int count)` -- `(ReadOnly-)Span.SkipWhile(Predicate condition)` -- `(ReadOnly-)Span.TakeWhile(Predicate condition)` -- `(Readonly-)Span.First()` -- `(Readonly-)Span.First(Predicate predicate)` -- `(Readonly-)Span.FirstOrDefault()` -- `(Readonly-)Span.FirstOrDefault(Predicate predicate)` -- `(Readonly-)Span.FirstOrDefault(T defaultValue)` -- `(Readonly-)Span.FirstOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.Last()` -- `(Readonly-)Span.Last(Predicate predicate)` -- `(Readonly-)Span.LastOrDefault()` -- `(Readonly-)Span.LastOrDefault(Predicate predicate)` -- `(Readonly-)Span.LastOrDefault(T defaultValue)` -- `(Readonly-)Span.LastOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.Single()` -- `(Readonly-)Span.Single(Predicate predicate)` -- `(Readonly-)Span.SingleOrDefault()` -- `(Readonly-)Span.SingleOrDefault(Predicate predicate)` -- `(Readonly-)Span.SingleOrDefault(T defaultValue)` -- `(Readonly-)Span.SingleOrDefault(Predicate predicate, T defaultValue)` -- `(Readonly-)Span.ElementAt(int index)` -- `(Readonly-)Span.ElementAt(Index index)` -- `(Readonly-)Span.ElementAtOrDefault(int index)` -- `(Readonly-)Span.ElementAtOrDefault(Index index)` -- `(Readonly-)Span.ElementAtOrDefault(int index, T defaultValue)` -- `(Readonly-)Span.ElementAtOrDefault(Index index, T defaultValue)` -- `(Readonly-)Span.Min()` -- `(Readonly-)Span.Min(Func selector)` -- `(Readonly-)Span.MinBy(Func keySelector)` -- `(Readonly-)Span.MinBy(Func keySelector, IComparer comparer)` -- `(Readonly-)Span.Max()` -- `(Readonly-)Span.Max(Func selector)` -- `(Readonly-)Span.MaxBy(Func keySelector)` -- `(Readonly-)Span.MaxBy(Func keySelector, IComparer comparer)` +**SpanExtensions.Net** aims to help developers use `ReadonlySpan` and `Span` more **productively**, **efficiently** and **safely** and write overall more **performant** Programs. ## Contributing -Thank you for your interest in contributing to this project! - You may contribute on [Github](https://github.com/draconware-dev/SpanExtensions.Net). +Thank you for your interest in contributing to this project! - You may contribute on [Github](https://github.com/draconware-dev/SpanExtensions.Net). + ## License -Copyright (c) draconware-dev. All rights reserved. +Copyright (c) draconware-dev. All rights reserved. Licensed under the [MIT](https://github.com/draconware-dev/SpanExtensions.Net/blob/main/LICENSE) license. diff --git a/src/SpanExtensions.csproj b/src/SpanExtensions.csproj index b44d033..eddcba7 100644 --- a/src/SpanExtensions.csproj +++ b/src/SpanExtensions.csproj @@ -10,19 +10,17 @@ dragon-cs draconware - ReadonlySpan<T> and Span<T> are great Types in C#, but unfortunately working with them can sometimes be sort of a hassle and some use cases seem straight up impossible, even though they are not. - - SpanExtensions.Net aims to help developers use ReadonlySpan<T> and Span<T> more productively, efficiently and safely and write overall more performant Programs. - Never again switch back to using string instead of ReadonlySpan<T>, just because the method you seek is not supported. - + + SpanExtensions.Net aims to help developers use ReadonlySpan<T> and Span<T> more productively, efficiently and safely and write overall more performant Programs. + https://github.com/draconware-dev/SpanExtensions.Net True - Copyright (c) 2024 draconware-dev + Copyright (c) 2025 draconware-dev Span;Performance;Extension;String https://github.com/draconware-dev/SpanExtensions.Net/blob/main/Changelog.md LICENSE - 1.5.1 + 2.0.0 SpanExtensions.Net README.md icon.png From 6df54d43e6e23eed813e8cf22a7cf663c38b9a68 Mon Sep 17 00:00:00 2001 From: dragon7307 Date: Sun, 28 Sep 2025 22:30:00 +0200 Subject: [PATCH 15/15] increased the vectorization threshold --- src/Extensions/ReadOnlySpan/Linq/Max.cs | 4 ++-- src/Extensions/ReadOnlySpan/Linq/Min.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Extensions/ReadOnlySpan/Linq/Max.cs b/src/Extensions/ReadOnlySpan/Linq/Max.cs index 85812b7..b9ef6ee 100644 --- a/src/Extensions/ReadOnlySpan/Linq/Max.cs +++ b/src/Extensions/ReadOnlySpan/Linq/Max.cs @@ -86,7 +86,7 @@ public static T Max(this ReadOnlySpan source) where T : IComparable return result; } - if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count * 2) { ref T current = ref MemoryMarshal.GetReference(source); ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); @@ -117,7 +117,7 @@ public static T Max(this ReadOnlySpan source) where T : IComparable return result; } - if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count * 4) { ref T current = ref MemoryMarshal.GetReference(source); ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count); diff --git a/src/Extensions/ReadOnlySpan/Linq/Min.cs b/src/Extensions/ReadOnlySpan/Linq/Min.cs index 2b6473b..9067e6b 100644 --- a/src/Extensions/ReadOnlySpan/Linq/Min.cs +++ b/src/Extensions/ReadOnlySpan/Linq/Min.cs @@ -85,7 +85,7 @@ public static T Min(this ReadOnlySpan source) where T : IComparable return result; } - if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count) + if(Vector128.IsHardwareAccelerated && Vector128.IsSupported && source.Length > Vector128.Count * 2) { ref T current = ref MemoryMarshal.GetReference(source); ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector128.Count); @@ -115,8 +115,8 @@ public static T Min(this ReadOnlySpan source) where T : IComparable return result; } - - if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count) + + if(Vector64.IsHardwareAccelerated && Vector64.IsSupported && source.Length > Vector64.Count * 4) { ref T current = ref MemoryMarshal.GetReference(source); ref T secondToLast = ref Unsafe.Add(ref current, source.Length - Vector64.Count);