diff --git a/Directory.Build.props b/Directory.Build.props index 4663774..f56123e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,7 +12,7 @@ false false - 1.0.0 + 0.1.0 Kir_Antipov 2023 https://github.com/Kir-Antipov/Spanned diff --git a/src/Spanned/Collections/Generic/InlineArray.cs b/src/Spanned/Collections/Generic/InlineArray.cs index 802a346..6e4e2a1 100644 --- a/src/Spanned/Collections/Generic/InlineArray.cs +++ b/src/Spanned/Collections/Generic/InlineArray.cs @@ -6,13 +6,13 @@ namespace Spanned.Collections.Generic; /// Represents an inline array with one element. /// /// The type of the elements in the array. -[InlineArray(1)] +[StructLayout(LayoutKind.Sequential)] // Like it's gonna help... internal struct InlineArray1 { /// /// The reference to the first element. /// - private T? _element0; + private T _element0; /// /// Initializes a new instance of the struct with the specified elements. @@ -20,21 +20,34 @@ internal struct InlineArray1 /// public InlineArray1(T arg0) { - this[0] = arg0; + _element0 = arg0; } + + /// + /// Implicitly converts an to a . + /// + /// The to convert. + /// A representing the elements of the . + public static implicit operator ReadOnlySpan(in InlineArray1 inlineArray) + => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in inlineArray._element0), 1); } /// /// Represents an inline array with two elements. /// /// The type of the elements in the array. -[InlineArray(2)] +[StructLayout(LayoutKind.Sequential)] // Like it's gonna help... internal struct InlineArray2 { /// /// The reference to the first element. /// - private T? _element0; + private T _element0; + + /// + /// The reference to the second element. + /// + private T _element1; /// /// Initializes a new instance of the struct with the specified elements. @@ -42,22 +55,40 @@ internal struct InlineArray2 /// public InlineArray2(T arg0, T arg1) { - this[0] = arg0; - this[1] = arg1; + _element0 = arg0; + _element1 = arg1; } + + /// + /// Implicitly converts an to a . + /// + /// The to convert. + /// A representing the elements of the . + public static implicit operator ReadOnlySpan(in InlineArray2 inlineArray) + => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in inlineArray._element0), 2); } /// /// Represents an inline array with three elements. /// /// The type of the elements in the array. -[InlineArray(3)] +[StructLayout(LayoutKind.Sequential)] // Like it's gonna help... internal struct InlineArray3 { /// /// The reference to the first element. /// - private T? _element0; + private T _element0; + + /// + /// The reference to the second element. + /// + private T _element1; + + /// + /// The reference to the third element. + /// + private T _element2; /// /// Initializes a new instance of the struct with the specified elements. @@ -67,10 +98,18 @@ internal struct InlineArray3 /// The third element. public InlineArray3(T arg0, T arg1, T arg2) { - this[0] = arg0; - this[1] = arg1; - this[2] = arg2; + _element0 = arg0; + _element1 = arg1; + _element2 = arg2; } + + /// + /// Implicitly converts an to a . + /// + /// The to convert. + /// A representing the elements of the . + public static implicit operator ReadOnlySpan(in InlineArray3 inlineArray) + => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in inlineArray._element0), 3); } #pragma warning restore CA1823, CS0169, IDE0044, IDE0051 // `_element0` is used by the compiler. diff --git a/src/Spanned/Collections/Generic/ValueEnumerable.cs b/src/Spanned/Collections/Generic/ValueEnumerable.cs index 6f8563a..8c236e3 100644 --- a/src/Spanned/Collections/Generic/ValueEnumerable.cs +++ b/src/Spanned/Collections/Generic/ValueEnumerable.cs @@ -1,3 +1,5 @@ +using System.Collections; + namespace Spanned.Collections.Generic; /// @@ -45,19 +47,6 @@ public ValueEnumerable(T[]? array) _span = new(array); } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The list to create the from. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ValueEnumerable(List? list) - { - _enumerable = null; - _span = CollectionsMarshal.AsSpan(list); - } - /// /// Initializes a new instance of the struct. /// @@ -294,6 +283,19 @@ public static bool TryGetNonEnumeratedCount(this scoped ValueEnumerable return true; } - return source._enumerable.TryGetNonEnumeratedCount(out count); + if (source._enumerable is ICollection collection) + { + count = collection.Count; + return true; + } + + if (source._enumerable is ICollection nonGenericCollection) + { + count = nonGenericCollection.Count; + return true; + } + + count = 0; + return false; } } diff --git a/src/Spanned/Collections/ValueBitArray.cs b/src/Spanned/Collections/ValueBitArray.cs index a488ca0..886a8b3 100644 --- a/src/Spanned/Collections/ValueBitArray.cs +++ b/src/Spanned/Collections/ValueBitArray.cs @@ -14,7 +14,7 @@ internal readonly ref struct ValueBitArray /// /// The reference to the start of the . /// - private readonly ref int _start; + private readonly Span _ints; /// /// The number of bits contained in the . @@ -36,7 +36,7 @@ public ValueBitArray(Span bytes) /// The span of s to create a bit array from. public ValueBitArray(Span ints) { - _start = ref MemoryMarshal.GetReference(ints); + _ints = ints; _length = checked(ints.Length * BitsPerInt32); } @@ -93,7 +93,7 @@ public bool this[int index] uint bitIndex = (uint)index % BitsPerInt32; int bitMask = 1 << (int)bitIndex; - ref int intValue = ref Unsafe.Add(ref _start, (nint)intIndex); + ref int intValue = ref Unsafe.Add(ref MemoryMarshal.GetReference(_ints), (nint)intIndex); intValue = value ? (intValue | bitMask) : (intValue & ~bitMask); } @@ -106,18 +106,14 @@ public bool this[int index] uint bitIndex = (uint)index % BitsPerInt32; int bitMask = 1 << (int)bitIndex; - return (Unsafe.Add(ref _start, (nint)intIndex) & bitMask) != 0; + return (Unsafe.Add(ref MemoryMarshal.GetReference(_ints), (nint)intIndex) & bitMask) != 0; } } /// /// Clears all the bits in the bit array. /// - public void Clear() - { - Span bytes = MemoryMarshal.CreateSpan(ref _start, _length / BitsPerInt32); - bytes.Clear(); - } + public void Clear() => _ints.Clear(); /// /// Converts this instance to a boolean array. diff --git a/src/Spanned/Compat/System/Diagnostics/CodeAnalysis/StringSyntaxAttribute.cs b/src/Spanned/Compat/System/Diagnostics/CodeAnalysis/StringSyntaxAttribute.cs new file mode 100644 index 0000000..ba85526 --- /dev/null +++ b/src/Spanned/Compat/System/Diagnostics/CodeAnalysis/StringSyntaxAttribute.cs @@ -0,0 +1,23 @@ +namespace System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +internal sealed class StringSyntaxAttribute : Attribute +{ + public StringSyntaxAttribute(string syntax) + { + Syntax = syntax; + Arguments = Array.Empty(); + } + + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + Syntax = syntax; + Arguments = arguments; + } + + public string Syntax { get; } + + public object?[] Arguments { get; } + + public const string CompositeFormat = nameof(CompositeFormat); +} diff --git a/src/Spanned/Compat/System/Numerics/ByteNumber.cs b/src/Spanned/Compat/System/Numerics/ByteNumber.cs new file mode 100644 index 0000000..491c4c5 --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/ByteNumber.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct ByteNumber : INumber +{ + public byte MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => byte.MinValue; + } + + public byte MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => byte.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte Add(byte left, byte right) => (byte)(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte AddChecked(byte left, byte right) => checked((byte)(left + right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(byte value) => false; +} diff --git a/src/Spanned/Compat/System/Numerics/DecimalNumber.cs b/src/Spanned/Compat/System/Numerics/DecimalNumber.cs new file mode 100644 index 0000000..349652a --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/DecimalNumber.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct DecimalNumber : INumber +{ + public decimal MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => decimal.MinValue; + } + + public decimal MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => decimal.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public decimal Add(decimal left, decimal right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public decimal AddChecked(decimal left, decimal right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(decimal value) => value < 0m; +} diff --git a/src/Spanned/Compat/System/Numerics/DoubleNumber.cs b/src/Spanned/Compat/System/Numerics/DoubleNumber.cs new file mode 100644 index 0000000..4057afc --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/DoubleNumber.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct DoubleNumber : INumber +{ + public double MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => double.MinValue; + } + + public double MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => double.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double Add(double left, double right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double AddChecked(double left, double right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(double value) => value < 0d; +} diff --git a/src/Spanned/Compat/System/Numerics/INumber.cs b/src/Spanned/Compat/System/Numerics/INumber.cs new file mode 100644 index 0000000..80a88b5 --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/INumber.cs @@ -0,0 +1,14 @@ +namespace System.Numerics; + +internal interface INumber where T : struct +{ + T MinValue { get; } + + T MaxValue { get; } + + bool IsNegative(T value); + + T Add(T left, T right); + + T AddChecked(T left, T right); +} diff --git a/src/Spanned/Compat/System/Numerics/Int16Number.cs b/src/Spanned/Compat/System/Numerics/Int16Number.cs new file mode 100644 index 0000000..4c1d44d --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/Int16Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct Int16Number : INumber +{ + public short MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => short.MinValue; + } + + public short MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => short.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short Add(short left, short right) => (short)(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short AddChecked(short left, short right) => checked((short)(left + right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(short value) => value < (short)0; +} diff --git a/src/Spanned/Compat/System/Numerics/Int32Number.cs b/src/Spanned/Compat/System/Numerics/Int32Number.cs new file mode 100644 index 0000000..35537f4 --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/Int32Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct Int32Number : INumber +{ + public int MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => int.MinValue; + } + + public int MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => int.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Add(int left, int right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int AddChecked(int left, int right) => checked(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(int value) => value < 0; +} diff --git a/src/Spanned/Compat/System/Numerics/Int64Number.cs b/src/Spanned/Compat/System/Numerics/Int64Number.cs new file mode 100644 index 0000000..909ad8b --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/Int64Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct Int64Number : INumber +{ + public long MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => long.MinValue; + } + + public long MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => long.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Add(long left, long right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long AddChecked(long left, long right) => checked(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(long value) => value < 0L; +} diff --git a/src/Spanned/Compat/System/Numerics/SByteNumber.cs b/src/Spanned/Compat/System/Numerics/SByteNumber.cs new file mode 100644 index 0000000..53630cf --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/SByteNumber.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct SByteNumber : INumber +{ + public sbyte MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => sbyte.MinValue; + } + + public sbyte MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => sbyte.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte Add(sbyte left, sbyte right) => (sbyte)(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte AddChecked(sbyte left, sbyte right) => checked((sbyte)(left + right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(sbyte value) => value < (sbyte)0; +} diff --git a/src/Spanned/Compat/System/Numerics/SingleNumber.cs b/src/Spanned/Compat/System/Numerics/SingleNumber.cs new file mode 100644 index 0000000..601ca5a --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/SingleNumber.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct SingleNumber : INumber +{ + public float MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => float.MinValue; + } + + public float MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => float.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Add(float left, float right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float AddChecked(float left, float right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(float value) => value < 0f; +} diff --git a/src/Spanned/Compat/System/Numerics/UInt16Number.cs b/src/Spanned/Compat/System/Numerics/UInt16Number.cs new file mode 100644 index 0000000..45eddb0 --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/UInt16Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct UInt16Number : INumber +{ + public ushort MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ushort.MinValue; + } + + public ushort MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ushort.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Add(ushort left, ushort right) => (ushort)(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort AddChecked(ushort left, ushort right) => checked((ushort)(left + right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(ushort value) => false; +} diff --git a/src/Spanned/Compat/System/Numerics/UInt32Number.cs b/src/Spanned/Compat/System/Numerics/UInt32Number.cs new file mode 100644 index 0000000..6a52cb0 --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/UInt32Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct UInt32Number : INumber +{ + public uint MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => uint.MinValue; + } + + public uint MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => uint.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Add(uint left, uint right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint AddChecked(uint left, uint right) => checked(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(uint value) => false; +} diff --git a/src/Spanned/Compat/System/Numerics/UInt64Number.cs b/src/Spanned/Compat/System/Numerics/UInt64Number.cs new file mode 100644 index 0000000..c019fae --- /dev/null +++ b/src/Spanned/Compat/System/Numerics/UInt64Number.cs @@ -0,0 +1,25 @@ +namespace System.Numerics; + +internal readonly struct UInt64Number : INumber +{ + public ulong MinValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ulong.MinValue; + } + + public ulong MaxValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ulong.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong Add(ulong left, ulong right) => left + right; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong AddChecked(ulong left, ulong right) => checked(left + right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNegative(ulong value) => false; +} diff --git a/src/Spanned/Compat/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs b/src/Spanned/Compat/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs new file mode 100644 index 0000000..a8380fe --- /dev/null +++ b/src/Spanned/Compat/System/Runtime/CompilerServices/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,12 @@ +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +internal sealed class CallerArgumentExpressionAttribute : Attribute +{ + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } +} diff --git a/src/Spanned/Compat/System/Runtime/CompilerServices/SkipLocalsInitAttribute.cs b/src/Spanned/Compat/System/Runtime/CompilerServices/SkipLocalsInitAttribute.cs new file mode 100644 index 0000000..3c161f8 --- /dev/null +++ b/src/Spanned/Compat/System/Runtime/CompilerServices/SkipLocalsInitAttribute.cs @@ -0,0 +1,14 @@ +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Module + | AttributeTargets.Class + | AttributeTargets.Struct + | AttributeTargets.Interface + | AttributeTargets.Constructor + | AttributeTargets.Method + | AttributeTargets.Property + | AttributeTargets.Event, Inherited = false)] +internal sealed class SkipLocalsInitAttribute : Attribute +{ + public SkipLocalsInitAttribute() { } +} diff --git a/src/Spanned/Helpers/CollectionHelper.cs b/src/Spanned/Helpers/CollectionHelper.cs index f1e6d94..ae21c6f 100644 --- a/src/Spanned/Helpers/CollectionHelper.cs +++ b/src/Spanned/Helpers/CollectionHelper.cs @@ -8,9 +8,6 @@ internal static class CollectionHelper /// /// The maximum allowed capacity for a collection. /// - /// - /// Represents the same value as . - /// public const int MaxCapacity = 0x7FFFFFC7; /// diff --git a/src/Spanned/Helpers/CompositeFormatHelper.cs b/src/Spanned/Helpers/CompositeFormatHelper.cs deleted file mode 100644 index acddf8e..0000000 --- a/src/Spanned/Helpers/CompositeFormatHelper.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Text; - -namespace Spanned.Helpers; - -/// -/// Provides utility functions for operations related to the class. -/// -internal static class CompositeFormatHelper -{ - /// - /// Returns the sum of the lengths of all of the literals in the segments. - /// - /// The instance. - /// - /// The sum of the lengths of all of the literals in the segments. - /// - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_literalLength")] - public static extern ref int LiteralLength(this CompositeFormat format); - - /// - /// Returns the number of segments in that represent format holes. - /// - /// The instance. - /// - /// The number of segments in that represent format holes. - /// - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_formattedCount")] - public static extern ref int FormattedCount(this CompositeFormat format); - - /// - /// Returns the parsed segments that make up the composite format string. - /// - /// - /// Every segment represents either a literal or a format hole, based on whether Literal - /// is non-null or ArgIndex is non-negative. - /// - /// The instance. - /// - /// The parsed segments that make up the composite format string. - /// - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_segments")] - public static extern ref (string? Literal, int ArgIndex, int Alignment, string? Format)[] Segments(this CompositeFormat format); - - /// - /// Throws an exception if the specified number of arguments is fewer than the number required. - /// - /// The instance. - /// The number of arguments provided by the caller. - /// An insufficient number of arguments were provided. - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = nameof(ValidateNumberOfArgs))] - public static extern void ValidateNumberOfArgs(this CompositeFormat format, int numArgs); -} diff --git a/src/Spanned/Helpers/EnumHelper.cs b/src/Spanned/Helpers/EnumHelper.cs deleted file mode 100644 index 5a5a994..0000000 --- a/src/Spanned/Helpers/EnumHelper.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Spanned.Helpers; - -/// -/// Provides utility functions for operations related to enums. -/// -internal static class EnumHelper -{ - /// - /// - /// This is an unconstrained version of the method. - /// - public static bool TryFormatUnconstrained(TEnum value, Span destination, out int charsWritten, ReadOnlySpan format = default) - { -#if NET9_0_OR_GREATER - return __Invoke(null, value, destination, out charsWritten, format); - - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = nameof(TryFormatUnconstrained))] - static extern bool __Invoke(Enum? targetType, TEnum value, Span destination, out int charsWritten, ReadOnlySpan format); -#else - if (value is null) - { - charsWritten = 0; - return false; - } - - return ((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, provider: null); -#endif - } -} diff --git a/src/Spanned/Spanned.csproj b/src/Spanned/Spanned.csproj index c080e8c..a5bc4bf 100644 --- a/src/Spanned/Spanned.csproj +++ b/src/Spanned/Spanned.csproj @@ -1,7 +1,7 @@ - net8.0 + netstandard2.1 @@ -30,6 +30,10 @@ $(NoWarn);CS0809 + + + + @@ -39,7 +43,6 @@ - diff --git a/src/Spanned/Spans.Average.cs b/src/Spanned/Spans.Average.cs index 5c22111..6d89938 100644 --- a/src/Spanned/Spans.Average.cs +++ b/src/Spanned/Spans.Average.cs @@ -205,56 +205,6 @@ public static double Average(this scoped ReadOnlySpan span) return 0; } - /// - /// Computes the average of a span of values. - /// - /// A span of values to calculate the average of. - /// - public static double Average(this scoped Span span) - { - if (!span.IsEmpty) - return span.LongSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - public static double Average(this scoped ReadOnlySpan span) - { - if (!span.IsEmpty) - return span.LongSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - /// Computes the average of a span of values. - /// - /// A span of values to calculate the average of. - /// - [CLSCompliant(false)] - public static double Average(this scoped Span span) - { - if (!span.IsEmpty) - return span.LongSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - [CLSCompliant(false)] - public static double Average(this scoped ReadOnlySpan span) - { - if (!span.IsEmpty) - return span.LongSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - /// /// Computes the average of a span of values. /// @@ -327,47 +277,6 @@ public static decimal Average(this scoped ReadOnlySpan span) return 0; } - /// - public static TResult Average(this scoped Span span) where TSource : INumberBase where TResult : INumberBase => Average(span); - - /// - public static TResult Average(this scoped ReadOnlySpan span) where TSource : INumberBase where TResult : INumberBase => Average(span); - - /// - /// Computes the average of a span of generic values. - /// - /// The type of elements in the source span. - /// The type used for intermediate accumulation. - /// The type of the result. - /// A span of generic values to calculate the average of. - /// The average of the span of values. - /// contains no elements. - /// The addition operation in a checked context resulted in an overflow. - public static TResult Average(this scoped Span span) - where TSource : INumberBase - where TAccumulator : INumberBase - where TResult : INumberBase - { - if (!span.IsEmpty) - return TResult.CreateChecked(span.LongSum()) / TResult.CreateChecked(span.Length); - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return TResult.Zero; - } - - /// - public static TResult Average(this scoped ReadOnlySpan span) - where TSource : INumberBase - where TAccumulator : INumberBase - where TResult : INumberBase - { - if (!span.IsEmpty) - return TResult.CreateChecked(span.LongSum()) / TResult.CreateChecked(span.Length); - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return TResult.Zero; - } - /// /// Computes the average of a span of values. /// @@ -572,89 +481,4 @@ public static double UnsafeAverage(this scoped ReadOnlySpan span) ThrowHelper.ThrowInvalidOperationException_NoElements(); return 0; } - - /// - /// Computes the average of a span of values. - /// - /// A span of values to calculate the average of. - /// - public static double UnsafeAverage(this scoped Span span) - { - if (!span.IsEmpty) - return span.UnsafeSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - public static double UnsafeAverage(this scoped ReadOnlySpan span) - { - if (!span.IsEmpty) - return span.UnsafeSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - /// Computes the average of a span of values. - /// - /// A span of values to calculate the average of. - /// - [CLSCompliant(false)] - public static double UnsafeAverage(this scoped Span span) - { - if (!span.IsEmpty) - return span.UnsafeSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - [CLSCompliant(false)] - public static double UnsafeAverage(this scoped ReadOnlySpan span) - { - if (!span.IsEmpty) - return span.UnsafeSum() / (double)span.Length; - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return 0; - } - - /// - /// Computes the average of a span of generic values. - /// - /// - /// This method does not account for overflow. - /// Use with caution and ensure that values in the span cannot cause an overflow if it is not desirable. - /// - /// The type of elements in the source span. - /// The type of the result. - /// A span of generic values to calculate the average of. - /// The average of the span of values. - /// contains no elements. - public static TResult UnsafeAverage(this scoped Span span) - where TSource : INumberBase - where TResult : INumberBase - { - if (!span.IsEmpty) - return TResult.CreateChecked(span.UnsafeSum()) / TResult.CreateChecked(span.Length); - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return default; - } - - /// - public static TResult UnsafeAverage(this scoped ReadOnlySpan span) - where TSource : INumberBase - where TResult : INumberBase - { - if (!span.IsEmpty) - return TResult.CreateChecked(span.UnsafeSum()) / TResult.CreateChecked(span.Length); - - ThrowHelper.ThrowInvalidOperationException_NoElements(); - return default; - } } diff --git a/src/Spanned/Spans.Contains.cs b/src/Spanned/Spans.Contains.cs index 0602f06..5b084b1 100644 --- a/src/Spanned/Spans.Contains.cs +++ b/src/Spanned/Spans.Contains.cs @@ -19,43 +19,37 @@ public static bool Contains(this scoped Span span, T value, IEqualityCompa if (comparer is null || comparer == EqualityComparer.Default) { if (typeof(T) == typeof(byte)) - return MemoryExtensions.Contains(UnsafeCast(span), (byte)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (byte)(object)value!) >= 0; if (typeof(T) == typeof(sbyte)) - return MemoryExtensions.Contains(UnsafeCast(span), (sbyte)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (sbyte)(object)value!) >= 0; if (typeof(T) == typeof(short)) - return MemoryExtensions.Contains(UnsafeCast(span), (short)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (short)(object)value!) >= 0; if (typeof(T) == typeof(ushort)) - return MemoryExtensions.Contains(UnsafeCast(span), (ushort)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (ushort)(object)value!) >= 0; if (typeof(T) == typeof(int)) - return MemoryExtensions.Contains(UnsafeCast(span), (int)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (int)(object)value!) >= 0; if (typeof(T) == typeof(uint)) - return MemoryExtensions.Contains(UnsafeCast(span), (uint)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (uint)(object)value!) >= 0; if (typeof(T) == typeof(long)) - return MemoryExtensions.Contains(UnsafeCast(span), (long)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (long)(object)value!) >= 0; if (typeof(T) == typeof(ulong)) - return MemoryExtensions.Contains(UnsafeCast(span), (ulong)(object)value!); - - if (typeof(T) == typeof(nint)) - return MemoryExtensions.Contains(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.Contains(UnsafeCast(span), (nuint)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (ulong)(object)value!) >= 0; if (typeof(T) == typeof(float)) - return MemoryExtensions.Contains(UnsafeCast(span), (float)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (float)(object)value!) >= 0; if (typeof(T) == typeof(double)) - return MemoryExtensions.Contains(UnsafeCast(span), (double)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (double)(object)value!) >= 0; if (typeof(T) == typeof(char)) - return MemoryExtensions.Contains(UnsafeCast(span), (char)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (char)(object)value!) >= 0; if (value is IEquatable) return IndexOfEquatable(ref MemoryMarshal.GetReference(span), span.Length, value) >= 0; @@ -71,43 +65,37 @@ public static bool Contains(this scoped ReadOnlySpan span, T value, IEqual if (comparer is null || comparer == EqualityComparer.Default) { if (typeof(T) == typeof(byte)) - return MemoryExtensions.Contains(UnsafeCast(span), (byte)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (byte)(object)value!) >= 0; if (typeof(T) == typeof(sbyte)) - return MemoryExtensions.Contains(UnsafeCast(span), (sbyte)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (sbyte)(object)value!) >= 0; if (typeof(T) == typeof(short)) - return MemoryExtensions.Contains(UnsafeCast(span), (short)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (short)(object)value!) >= 0; if (typeof(T) == typeof(ushort)) - return MemoryExtensions.Contains(UnsafeCast(span), (ushort)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (ushort)(object)value!) >= 0; if (typeof(T) == typeof(int)) - return MemoryExtensions.Contains(UnsafeCast(span), (int)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (int)(object)value!) >= 0; if (typeof(T) == typeof(uint)) - return MemoryExtensions.Contains(UnsafeCast(span), (uint)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (uint)(object)value!) >= 0; if (typeof(T) == typeof(long)) - return MemoryExtensions.Contains(UnsafeCast(span), (long)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (long)(object)value!) >= 0; if (typeof(T) == typeof(ulong)) - return MemoryExtensions.Contains(UnsafeCast(span), (ulong)(object)value!); - - if (typeof(T) == typeof(nint)) - return MemoryExtensions.Contains(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.Contains(UnsafeCast(span), (nuint)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (ulong)(object)value!) >= 0; if (typeof(T) == typeof(float)) - return MemoryExtensions.Contains(UnsafeCast(span), (float)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (float)(object)value!) >= 0; if (typeof(T) == typeof(double)) - return MemoryExtensions.Contains(UnsafeCast(span), (double)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (double)(object)value!) >= 0; if (typeof(T) == typeof(char)) - return MemoryExtensions.Contains(UnsafeCast(span), (char)(object)value!); + return MemoryExtensions.IndexOf(UnsafeCast(span), (char)(object)value!) >= 0; if (value is IEquatable) return IndexOfEquatable(ref MemoryMarshal.GetReference(span), span.Length, value) >= 0; diff --git a/src/Spanned/Spans.Extremum.cs b/src/Spanned/Spans.Extremum.cs index df9320e..e24fe09 100644 --- a/src/Spanned/Spans.Extremum.cs +++ b/src/Spanned/Spans.Extremum.cs @@ -13,11 +13,9 @@ public static partial class Spans /// . /// contains no elements. private static T Extremum(ref T searchSpace, int length) - where T : struct, IBinaryNumber - where TExtremum : IExtremum + where T : struct + where TExtremum : struct, IExtremum { - Debug.Assert(Vector.IsSupported); - if (length == 0) ThrowHelper.ThrowInvalidOperationException_NoElements(); @@ -26,7 +24,7 @@ private static T Extremum(ref T searchSpace, int length) // Therefore, when `length == Vector.Count` a vectorized solution // would just end up performing unnecessary operations, becoming // slower than the regular loop without those. - if (!Vector128.IsHardwareAccelerated || length <= Vector128.Count) + if (!Vector.IsHardwareAccelerated || length <= Vector.Count) { ref T current = ref searchSpace; ref T end = ref Unsafe.Add(ref current, length); @@ -36,72 +34,30 @@ private static T Extremum(ref T searchSpace, int length) while (Unsafe.IsAddressLessThan(ref current, ref end)) { - if (TExtremum.Compare(current, extremumValue)) + if (default(TExtremum).Compare(current, extremumValue)) extremumValue = current; current = ref Unsafe.Add(ref current, (nint)1); } return extremumValue; } - else if (!Vector256.IsHardwareAccelerated || length <= Vector256.Count) - { - ref T current = ref Unsafe.Add(ref searchSpace, (nint)Vector128.Count); - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - Vector128 extremum = Vector128.LoadUnsafe(ref searchSpace); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - extremum = TExtremum.Compare(extremum, Vector128.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); - } - extremum = TExtremum.Compare(extremum, Vector128.LoadUnsafe(ref lastVectorStart)); - - T extremumValue = extremum[0]; - for (int i = 1; i < Vector128.Count; i++) - { - if (TExtremum.Compare(extremum[i], extremumValue)) - extremumValue = extremum[i]; - } - return extremumValue; - } - else if (!Vector512.IsHardwareAccelerated || length <= Vector512.Count) - { - ref T current = ref Unsafe.Add(ref searchSpace, (nint)Vector256.Count); - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); - Vector256 extremum = Vector256.LoadUnsafe(ref searchSpace); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - extremum = TExtremum.Compare(extremum, Vector256.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - } - extremum = TExtremum.Compare(extremum, Vector256.LoadUnsafe(ref lastVectorStart)); - - T extremumValue = extremum[0]; - for (int i = 1; i < Vector256.Count; i++) - { - if (TExtremum.Compare(extremum[i], extremumValue)) - extremumValue = extremum[i]; - } - return extremumValue; - } else { - ref T current = ref Unsafe.Add(ref searchSpace, (nint)Vector512.Count); - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector512.Count); - Vector512 extremum = Vector512.LoadUnsafe(ref searchSpace); + ref T current = ref Unsafe.Add(ref searchSpace, (nint)Vector.Count); + ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector.Count); + Vector extremum = new(MemoryMarshal.CreateSpan(ref searchSpace, Vector.Count)); while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) { - extremum = TExtremum.Compare(extremum, Vector512.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); + extremum = default(TExtremum).Compare(extremum, new(MemoryMarshal.CreateSpan(ref current, Vector.Count))); + current = ref Unsafe.Add(ref current, (nint)Vector.Count); } - extremum = TExtremum.Compare(extremum, Vector512.LoadUnsafe(ref lastVectorStart)); + extremum = default(TExtremum).Compare(extremum, new(MemoryMarshal.CreateSpan(ref lastVectorStart, Vector.Count))); T extremumValue = extremum[0]; - for (int i = 1; i < Vector512.Count; i++) + for (int i = 1; i < Vector.Count; i++) { - if (TExtremum.Compare(extremum[i], extremumValue)) + if (default(TExtremum).Compare(extremum[i], extremumValue)) extremumValue = extremum[i]; } return extremumValue; @@ -113,7 +69,7 @@ private static T Extremum(ref T searchSpace, int length) /// to determine the extremum (minimum or maximum). /// /// The type of values to compare. - private interface IExtremum where T : struct, IBinaryNumber + private interface IExtremum where T : struct { /// /// Compares two values of type to determine which one @@ -125,7 +81,7 @@ private interface IExtremum where T : struct, IBinaryNumber /// true if qualifies as the extremum; /// otherwise, false. /// - static abstract bool Compare(T left, T right); + bool Compare(T left, T right); /// /// Computes the extremum of two vectors on a per-element basis. @@ -133,12 +89,6 @@ private interface IExtremum where T : struct, IBinaryNumber /// The vector to compare with . /// The vector to compare with . /// A vector whose elements are the extremum of the corresponding elements in left and right. - static abstract Vector128 Compare(Vector128 left, Vector128 right); - - /// - static abstract Vector256 Compare(Vector256 left, Vector256 right); - - /// - static abstract Vector512 Compare(Vector512 left, Vector512 right); + Vector Compare(Vector left, Vector right); } } diff --git a/src/Spanned/Spans.FillSequential.cs b/src/Spanned/Spans.FillSequential.cs index 1a8b4e2..c3498f5 100644 --- a/src/Spanned/Spans.FillSequential.cs +++ b/src/Spanned/Spans.FillSequential.cs @@ -2,177 +2,19 @@ namespace Spanned; public static partial class Spans { - /// - /// A vectorized solution to fill a memory block with sequential values, - /// starting at the specified and incrementing - /// by the specified . - /// - /// The type of elements in the memory block. - /// The reference to the start of the memory block. - /// The length of the memory block. - /// The starting value for the sequential filling. - /// The increment value for the sequential filling. - /// is not supported. - private static void FillSequential(ref T searchSpace, int length, T offset, T step) - where T : struct, IBinaryNumber - { - Debug.Assert(Vector.IsSupported); - - if (length == 0) - return; - - nuint end = (nuint)length; - - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) - { - T previous = offset; - searchSpace = previous; - - for (nuint i = 1; i < end; i++) - { - previous += step; - Unsafe.Add(ref searchSpace, i) = previous; - } - } - else if (!Vector256.IsHardwareAccelerated || length < Vector256.Count) - { - Vector128 value = (T.IsNegative(step) ? Sequence128.Negative : Sequence128.Positive) * T.Abs(step) + Vector128.Create(offset); - Vector128 shift = Sequence128.Shift * step; - - nuint i = 0; - nuint lastVectorStart = end - (nuint)Vector128.Count; - do - { - value.StoreUnsafe(ref searchSpace, i); - value += shift; - i += (nuint)Vector128.Count; - } - while (i <= lastVectorStart); - - for (int j = 0; i < end; i++, j++) - Unsafe.Add(ref searchSpace, i) = value[j]; - } - else if (!Vector512.IsHardwareAccelerated || length < Vector512.Count) - { - Vector256 value = (T.IsNegative(step) ? Sequence256.Negative : Sequence256.Positive) * T.Abs(step) + Vector256.Create(offset); - Vector256 shift = Sequence256.Shift * step; - - nuint i = 0; - nuint lastVectorStart = end - (nuint)Vector256.Count; - do - { - value.StoreUnsafe(ref searchSpace, i); - value += shift; - i += (nuint)Vector256.Count; - } - while (i <= lastVectorStart); - - for (int j = 0; i < end; i++, j++) - Unsafe.Add(ref searchSpace, i) = value[j]; - } - else - { - Vector512 value = (T.IsNegative(step) ? Sequence512.Negative : Sequence512.Positive) * T.Abs(step) + Vector512.Create(offset); - Vector512 shift = Sequence512.Shift * step; - - nuint i = 0; - nuint lastVectorStart = end - (nuint)Vector512.Count; - do - { - value.StoreUnsafe(ref searchSpace, i); - value += shift; - i += (nuint)Vector512.Count; - } - while (i <= lastVectorStart); - - for (int j = 0; i < end; i++, j++) - Unsafe.Add(ref searchSpace, i) = value[j]; - } - } - /// /// Fills a generic span with sequential values, starting at the specified /// and incrementing by the specified . /// /// The type of elements in the . + /// The type of elements in the . /// The span to fill with sequential values. /// The starting value for the sequential filling. /// The increment value for the sequential filling. - public static void FillSequential(this scoped Span span, T offset, T step) - where T : IAdditionOperators + private static void FillSequential(this scoped Span span, T offset, T step) + where T : struct + where TNumber : struct, INumber { - if (typeof(T) == typeof(byte)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (byte)(object)offset, (byte)(object)step); - return; - } - - if (typeof(T) == typeof(sbyte)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (sbyte)(object)offset, (sbyte)(object)step); - return; - } - - if (typeof(T) == typeof(short)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (short)(object)offset, (short)(object)step); - return; - } - - if (typeof(T) == typeof(ushort)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (ushort)(object)offset, (ushort)(object)step); - return; - } - - if (typeof(T) == typeof(int)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (int)(object)offset, (int)(object)step); - return; - } - - if (typeof(T) == typeof(uint)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (uint)(object)offset, (uint)(object)step); - return; - } - - if (typeof(T) == typeof(long)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (long)(object)offset, (long)(object)step); - return; - } - - if (typeof(T) == typeof(ulong)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (ulong)(object)offset, (ulong)(object)step); - return; - } - - if (typeof(T) == typeof(nint)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (nint)(object)offset, (nint)(object)step); - return; - } - - if (typeof(T) == typeof(nuint)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (nuint)(object)offset, (nuint)(object)step); - return; - } - - if (typeof(T) == typeof(float)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (float)(object)offset, (float)(object)step); - return; - } - - if (typeof(T) == typeof(double)) - { - FillSequential(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length, (double)(object)offset, (double)(object)step); - return; - } - if (span.IsEmpty) return; @@ -181,151 +23,16 @@ public static void FillSequential(this scoped Span span, T offset, T step) for (int i = 1; i < span.Length; i++) { - previous += step; + previous = default(TNumber).Add(previous, step); span[i] = previous; } } - /// - /// Provides precomputed sequence vectors for using . - /// - /// The type for which sequences are generated. - private static class Sequence128 where T : struct, IBinaryNumber - { - /// - /// A sequence of positive values starting from zero. - /// - public static readonly Vector128 Positive; - - /// - /// A sequence of negative values starting from zero. - /// - public static readonly Vector128 Negative; - - /// - /// A value used for shifting a sequence vector. - /// - public static readonly Vector128 Shift; - - /// - /// Statically initializes the class. - /// - static Sequence128() - { - Span values = new T[Vector128.Count]; - values[0] = T.Zero; - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] + T.One; - } - Positive = Vector128.Create(values); - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] - T.One; - } - Negative = Vector128.Create(values); - - Shift = Vector128.One * T.CreateChecked(Vector128.Count); - } - } - - /// - /// Provides precomputed sequence vectors for using . - /// - /// The type for which sequences are generated. - private static class Sequence256 where T : struct, IBinaryNumber - { - /// - /// A sequence of positive values starting from zero. - /// - public static Vector256 Positive; - - /// - /// A sequence of negative values starting from zero. - /// - public static Vector256 Negative; - - /// - /// A value used for shifting a sequence vector. - /// - public static Vector256 Shift; - - /// - /// Statically initializes the class. - /// - static Sequence256() - { - Span values = new T[Vector256.Count]; - values[0] = T.Zero; - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] + T.One; - } - Positive = Vector256.Create(values); - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] - T.One; - } - Negative = Vector256.Create(values); - - Shift = Vector256.One * T.CreateChecked(Vector256.Count); - } - } - - /// - /// Provides precomputed sequence vectors for using . - /// - /// The type for which sequences are generated. - private static class Sequence512 where T : struct, IBinaryNumber - { - /// - /// A sequence of positive values starting from zero. - /// - public static Vector512 Positive; - - /// - /// A sequence of negative values starting from zero. - /// - public static Vector512 Negative; - - /// - /// A value used for shifting a sequence vector. - /// - public static Vector512 Shift; - - /// - /// Statically initializes the class. - /// - static Sequence512() - { - Span values = new T[Vector512.Count]; - values[0] = T.Zero; - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] + T.One; - } - Positive = Vector512.Create(values); - - for (int i = 1; i < values.Length; ++i) - { - values[i] = values[i - 1] - T.One; - } - Negative = Vector512.Create(values); - - Shift = Vector512.One * T.CreateChecked(Vector512.Count); - } - } - /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (byte)0, (byte)1); + public static void FillSequential(this scoped Span span) => FillSequential(span, (byte)0, (byte)1); /// /// Fills a span with sequential values, starting at the specified @@ -334,14 +41,14 @@ static Sequence512() /// The span to fill with sequential values. /// The starting value for the sequential filling. /// The increment value for the sequential filling. - public static void FillSequential(this scoped Span span, byte offset = 0, byte step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, byte offset = 0, byte step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. [CLSCompliant(false)] - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (sbyte)0, (sbyte)1); + public static void FillSequential(this scoped Span span) => FillSequential(span, (sbyte)0, (sbyte)1); /// /// Fills a span with sequential values, starting at the specified @@ -349,27 +56,27 @@ static Sequence512() /// /// [CLSCompliant(false)] - public static void FillSequential(this scoped Span span, sbyte offset = 0, sbyte step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, sbyte offset = 0, sbyte step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (short)0, (short)1); + public static void FillSequential(this scoped Span span) => FillSequential(span, (short)0, (short)1); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, short offset = 0, short step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, short offset = 0, short step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. [CLSCompliant(false)] - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (ushort)0, (ushort)1); + public static void FillSequential(this scoped Span span) => FillSequential(span, (ushort)0, (ushort)1); /// /// Fills a span with sequential values, starting at the specified @@ -377,27 +84,27 @@ static Sequence512() /// /// [CLSCompliant(false)] - public static void FillSequential(this scoped Span span, ushort offset = 0, ushort step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, ushort offset = 0, ushort step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0, 1); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0, 1); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, int offset = 0, int step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, int offset = 0, int step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. [CLSCompliant(false)] - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0u, 1u); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0u, 1u); /// /// Fills a span with sequential values, starting at the specified @@ -405,27 +112,27 @@ static Sequence512() /// /// [CLSCompliant(false)] - public static void FillSequential(this scoped Span span, uint offset = 0, uint step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, uint offset = 0, uint step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0L, 1L); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0L, 1L); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, long offset = 0, long step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, long offset = 0, long step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. [CLSCompliant(false)] - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0ul, 1ul); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0ul, 1ul); /// /// Fills a span with sequential values, starting at the specified @@ -433,79 +140,44 @@ static Sequence512() /// /// [CLSCompliant(false)] - public static void FillSequential(this scoped Span span, ulong offset = 0, ulong step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); - - /// - /// Fills a span with sequential values, starting at zero and incrementing by one. - /// - /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (nint)0, (nint)1); - - /// - /// Fills a span with sequential values, starting at the specified - /// and incrementing by the specified . - /// - /// - public static void FillSequential(this scoped Span span, nint offset = 0, nint step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); - - /// - /// Fills a span with sequential values, starting at zero and incrementing by one. - /// - /// The span to fill with sequential values. - [CLSCompliant(false)] - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, (nuint)0, (nuint)1); - - /// - /// Fills a span with sequential values, starting at the specified - /// and incrementing by the specified . - /// - /// - [CLSCompliant(false)] - public static void FillSequential(this scoped Span span, nuint offset = 0, nuint step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, ulong offset = 0, ulong step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0f, 1f); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0f, 1f); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, float offset = 0, float step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, float offset = 0, float step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, 0d, 1d); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0d, 1d); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, double offset = 0, double step = 1) => FillSequential(ref MemoryMarshal.GetReference(span), span.Length, offset, step); + public static void FillSequential(this scoped Span span, double offset = 0, double step = 1) => FillSequential(span, offset, step); /// /// Fills a span with sequential values, starting at zero and incrementing by one. /// /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) => FillSequential(span, 0m, 1m); + public static void FillSequential(this scoped Span span) => FillSequential(span, 0m, 1m); /// /// Fills a span with sequential values, starting at the specified /// and incrementing by the specified . /// /// - public static void FillSequential(this scoped Span span, decimal offset = 0, decimal step = 1) => FillSequential(span, offset, step); - - /// - /// Fills a generic span with sequential values, starting at zero and incrementing by one. - /// - /// The type of elements in the . - /// The span to fill with sequential values. - public static void FillSequential(this scoped Span span) where T : INumberBase => FillSequential(span, T.Zero, T.One); + public static void FillSequential(this scoped Span span, decimal offset = 0, decimal step = 1) => FillSequential(span, offset, step); } diff --git a/src/Spanned/Spans.IndexOf.cs b/src/Spanned/Spans.IndexOf.cs index fabb5bb..55caae1 100644 --- a/src/Spanned/Spans.IndexOf.cs +++ b/src/Spanned/Spans.IndexOf.cs @@ -42,12 +42,6 @@ public static int IndexOf(this scoped Span span, T value, IEqualityCompare if (typeof(T) == typeof(ulong)) return MemoryExtensions.IndexOf(UnsafeCast(span), (ulong)(object)value!); - if (typeof(T) == typeof(nint)) - return MemoryExtensions.IndexOf(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.IndexOf(UnsafeCast(span), (nuint)(object)value!); - if (typeof(T) == typeof(float)) return MemoryExtensions.IndexOf(UnsafeCast(span), (float)(object)value!); @@ -94,12 +88,6 @@ public static int IndexOf(this scoped ReadOnlySpan span, T value, IEqualit if (typeof(T) == typeof(ulong)) return MemoryExtensions.IndexOf(UnsafeCast(span), (ulong)(object)value!); - if (typeof(T) == typeof(nint)) - return MemoryExtensions.IndexOf(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.IndexOf(UnsafeCast(span), (nuint)(object)value!); - if (typeof(T) == typeof(float)) return MemoryExtensions.IndexOf(UnsafeCast(span), (float)(object)value!); @@ -130,26 +118,23 @@ public static int IndexOf(this scoped ReadOnlySpan span, T value, IEqualit /// The index of the occurrence of the value in the search space. If not found, returns -1. private static int IndexOf(ref T searchSpace, int length, T? value, IEqualityComparer? comparer) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. - nint end = (nint)length; - if (typeof(T).IsValueType && (comparer is null || comparer == EqualityComparer.Default)) { - for (nint i = 0; i < end; i++) + for (int i = 0; i < length; i++) { // Let JIT de-virtualize `Equals` calls for value types. - if (EqualityComparer.Default.Equals(value, Unsafe.Add(ref searchSpace, i))) - return (int)i; + if (EqualityComparer.Default.Equals(value!, Unsafe.Add(ref searchSpace, i))) + return i; } } else { comparer ??= EqualityComparer.Default; - for (nint i = 0; i < end; i++) + for (int i = 0; i < length; i++) { - if (comparer.Equals(value, Unsafe.Add(ref searchSpace, i))) - return (int)i; + if (comparer.Equals(value!, Unsafe.Add(ref searchSpace, i))) + return i; } } @@ -166,26 +151,23 @@ private static int IndexOf(ref T searchSpace, int length, T? value, IEquality /// The index of the occurrence of the value in the search space. If not found, returns -1. private static int IndexOfEquatable(ref T searchSpace, int length, T value) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. - nint end = (nint)length; - if (value is not null) { // Use IEquatable.Equals on structs/non-null values. - for (nint i = 0; i < end; i++) + for (int i = 0; i < length; i++) { // Let JIT de-virtualize `Equals` calls for value types. if (((IEquatable)value).Equals(Unsafe.Add(ref searchSpace, i))) - return (int)i; + return i; } } else { // Otherwise, search for null. - for (nint i = 0; i < end; i++) + for (int i = 0; i < length; i++) { if (Unsafe.Add(ref searchSpace, i) is null) - return (int)i; + return i; } } diff --git a/src/Spanned/Spans.LastIndexOf.cs b/src/Spanned/Spans.LastIndexOf.cs index 2b3ed88..412c3ae 100644 --- a/src/Spanned/Spans.LastIndexOf.cs +++ b/src/Spanned/Spans.LastIndexOf.cs @@ -42,12 +42,6 @@ public static int LastIndexOf(this scoped Span span, T value, IEqualityCom if (typeof(T) == typeof(ulong)) return MemoryExtensions.LastIndexOf(UnsafeCast(span), (ulong)(object)value!); - if (typeof(T) == typeof(nint)) - return MemoryExtensions.LastIndexOf(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.LastIndexOf(UnsafeCast(span), (nuint)(object)value!); - if (typeof(T) == typeof(float)) return MemoryExtensions.LastIndexOf(UnsafeCast(span), (float)(object)value!); @@ -94,12 +88,6 @@ public static int LastIndexOf(this scoped ReadOnlySpan span, T value, IEqu if (typeof(T) == typeof(ulong)) return MemoryExtensions.LastIndexOf(UnsafeCast(span), (ulong)(object)value!); - if (typeof(T) == typeof(nint)) - return MemoryExtensions.LastIndexOf(UnsafeCast(span), (nint)(object)value!); - - if (typeof(T) == typeof(nuint)) - return MemoryExtensions.LastIndexOf(UnsafeCast(span), (nuint)(object)value!); - if (typeof(T) == typeof(float)) return MemoryExtensions.LastIndexOf(UnsafeCast(span), (float)(object)value!); @@ -132,27 +120,25 @@ private static int LastIndexOf(ref T searchSpace, int length, T? value, IEqua { if (typeof(T).IsValueType && (comparer is null || comparer == EqualityComparer.Default)) { - for (nint i = (nint)length; i > 0;) + for (int i = length; i > 0;) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. i--; // Let JIT de-virtualize `Equals` calls for value types. - if (EqualityComparer.Default.Equals(value, Unsafe.Add(ref searchSpace, i))) - return (int)i; + if (EqualityComparer.Default.Equals(value!, Unsafe.Add(ref searchSpace, i))) + return i; } } else { comparer ??= EqualityComparer.Default; - for (nint i = (nint)length; i > 0;) + for (int i = length; i > 0;) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. i--; - if (comparer.Equals(value, Unsafe.Add(ref searchSpace, i))) - return (int)i; + if (comparer.Equals(value!, Unsafe.Add(ref searchSpace, i))) + return i; } } @@ -172,26 +158,24 @@ private static int LastIndexOfEquatable(ref T searchSpace, int length, T valu if (value is not null) { // Use IEquatable.Equals on structs/non-null values. - for (nint i = (nint)length; i > 0;) + for (int i = length; i > 0;) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. i--; // Let JIT de-virtualize `Equals` calls for value types. if (((IEquatable)value).Equals(Unsafe.Add(ref searchSpace, i))) - return (int)i; + return i; } } else { // Otherwise, search for null. - for (nint i = (nint)length; i > 0;) + for (int i = length; i > 0;) { - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. i--; if (Unsafe.Add(ref searchSpace, i) is null) - return (int)i; + return i; } } diff --git a/src/Spanned/Spans.LongSum.cs b/src/Spanned/Spans.LongSum.cs index aef96bc..5c2180e 100644 --- a/src/Spanned/Spans.LongSum.cs +++ b/src/Spanned/Spans.LongSum.cs @@ -5,79 +5,35 @@ public static partial class Spans /// /// A vectorized solution to compute the sum of the values in the specified memory block. /// - /// The type of the values. - /// The type used for intermediate accumulation. - /// The type of the result. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. /// The reference to the start of the memory block. /// The length of the memory block. /// The sum of the values in the memory block. - /// or is not supported. - private static TResult LongSum(ref TSource searchSpace, int length) - where TSource : struct, IBinaryNumber - where TAccumulator : struct, IBinaryNumber - where TResult : struct, IBinaryNumber - where TVectorAddition : IVectorAddition - where TVectorSummation : IVectorSummation + private static long LongSum(ref int searchSpace, int length) { - Debug.Assert(Vector.IsSupported && Vector.IsSupported); + long sum = 0; + ref int current = ref searchSpace; + ref int end = ref Unsafe.Add(ref current, length); - TResult sum = TResult.Zero; - ref TSource current = ref searchSpace; - ref TSource end = ref Unsafe.Add(ref current, length); - - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) - { - // The operation cannot be vectorized. - // Skip to the manual loop. - } - else if (!Vector256.IsHardwareAccelerated || length < Vector256.Count) + if (Vector.IsHardwareAccelerated && length >= Vector.Count) { - Vector128 sums = Vector128.Zero; - ref TSource lastVectorStart = ref Unsafe.Add(ref current, length - Vector128.Count); + Vector sums = Vector.Zero; + ref int lastVectorStart = ref Unsafe.Add(ref current, length - Vector.Count); do { - sums = TVectorAddition.Add(sums, Vector128.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); + Vector.Widen(new(MemoryMarshal.CreateSpan(ref current, Vector.Count)), out Vector currentA, out Vector currentB); + sums += currentA + currentB; + current = ref Unsafe.Add(ref current, (nint)Vector.Count); } while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - sum = TVectorSummation.Sum(sums); - } - else if (!Vector512.IsHardwareAccelerated || length < Vector512.Count) - { - Vector256 sums = Vector256.Zero; - ref TSource lastVectorStart = ref Unsafe.Add(ref current, length - Vector256.Count); - - do - { - sums = TVectorAddition.Add(sums, Vector256.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - - sum = TVectorSummation.Sum(sums); - } - else - { - Vector512 sums = Vector512.Zero; - ref TSource lastVectorStart = ref Unsafe.Add(ref current, length - Vector512.Count); - - do - { - sums = TVectorAddition.Add(sums, Vector512.LoadUnsafe(ref current)); - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - - sum = TVectorSummation.Sum(sums); + for (int i = 0; i < Vector.Count; i++) + sum += sums[i]; } while (Unsafe.IsAddressLessThan(ref current, ref end)) { - sum += TResult.CreateChecked(current); + sum += current; current = ref Unsafe.Add(ref current, (nint)1); } @@ -85,228 +41,80 @@ private static TResult LongSum - /// A vectorized solution to compute the sum of the values in the specified memory block. + /// Computes the sum of a span of values. /// - /// The type of the values. - /// The most optimal type used for intermediate accumulation. - /// - /// The least optimal type used for intermediate accumulation. - /// However, it's guaranteed to be reliable. - /// - /// The type of the result. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. - /// The reference to the start of the memory block. - /// The length of the memory block. - /// The sum of the values in the memory block. - /// - /// , or , or - /// is not supported. - /// - private static TResult LongSum< - TSource, TAccumulator1, TAccumulator2, TResult, - TVectorAddition1, TVectorSummation1, - TVectorAddition2, TVectorSummation2>(ref TSource searchSpace, int length) - where TSource : struct, IBinaryNumber, IMinMaxValue - where TAccumulator1 : struct, IBinaryNumber, IMinMaxValue - where TAccumulator2 : struct, IBinaryNumber, IMinMaxValue - where TResult : struct, IBinaryNumber - where TVectorAddition1 : IVectorAddition - where TVectorSummation1 : IVectorSummation - where TVectorAddition2 : IVectorAddition - where TVectorSummation2 : IVectorSummation + /// A span of values to calculate the sum of. + /// The sum of the values in the span. + [CLSCompliant(false)] + public static ulong LongSum(this scoped Span span) { - // See the next overload for more information on how and why this works. - - Debug.Assert(Unsafe.SizeOf() < Unsafe.SizeOf()); - - if (IsSafeAccumulator(length)) - { - return LongSum(ref searchSpace, length); - } - else - { - return LongSum(ref searchSpace, length); - } - } + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; - /// - /// A vectorized solution to compute the sum of the values in the specified memory block. - /// - /// The type of the values. - /// The most optimal type used for intermediate accumulation. - /// - /// The type used for intermediate accumulation that is slightly less optimal than - /// , but is still decent enough. - /// - /// - /// The least optimal type used for intermediate accumulation. - /// However, it's guaranteed to be reliable. - /// - /// The type of the result. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. - /// The type defines a mechanism for computing the sum of two vectors. - /// The type defines a mechanism for computing the sum of all elements in a vector. - /// The reference to the start of the memory block. - /// The length of the memory block. - /// The sum of the values in the memory block. - /// - /// , or , or - /// , or - /// is not supported. - /// - private static TResult LongSum< - TSource, TAccumulator1, TAccumulator2, TAccumulator3, TResult, - TVectorAddition1, TVectorSummation1, - TVectorAddition2, TVectorSummation2, - TVectorAddition3, TVectorSummation3>(ref TSource searchSpace, int length) - where TSource : struct, IBinaryNumber, IMinMaxValue - where TAccumulator1 : struct, IBinaryNumber, IMinMaxValue - where TAccumulator2 : struct, IBinaryNumber, IMinMaxValue - where TAccumulator3 : struct, IBinaryNumber, IMinMaxValue - where TResult : struct, IBinaryNumber - where TVectorAddition1 : IVectorAddition - where TVectorSummation1 : IVectorSummation - where TVectorAddition2 : IVectorAddition - where TVectorSummation2 : IVectorSummation - where TVectorAddition3 : IVectorAddition - where TVectorSummation3 : IVectorSummation - { - // The logic behind the entire `LongSum` concept lies in the fact that we can safely - // compute the sum for every integer type using a 64-bit integer as an accumulator - // (well, except for the 64-bit integers themselves). - // - // The condition `(long)uint.MaxValue * (long)length < long.MaxValue` is always `true`, - // since `length` cannot exceed `int.MaxValue`. - // - // The issue here is that widening a byte vector to a long vector is a relatively expensive - // operation, which compromises the performance benefits offered by vectorization. - // To optimize this, we can extend the original concept. For instance, we can determine - // how many 8-bit integers can reliably fit into a 16-bit integer: - // `ushort.MaxValue / byte.MaxValue == 257`. While this might seem like not a lot, operating - // on vectors changes the dynamics. A 128-bit vector can accommodate 8 16-bit integers, each - // operating as a standalone, independent accumulator (that's the whole point of vectorization), - // resulting in `257 * 8 == 2056` (or even 4112 with 256-bit vectors) 8-bit integers that we can - // process using only a single widening operation. - // - // Being able to process 2056 or 4112 values covers a significant range, considering that 1024, - // 2048, and 4096 are all common sizes for byte buffers. In case we encounter a larger size, we - // can utilize our favorite 32-bit integers as accumulators, providing us with - // `uint.MaxValue / byte.MaxValue * Vector128.Count == 67372036` (or 134744072 with 256-bit - // vectors) 8-bit integers that we can process using 2 widening operations per iteration. - // - // Finally, if someone is processing literally billions of bytes, we fallback to - // the reliable 64-bit accumulator, because, as mentioned before, it can handle it all. - // Even if it involves 3 widening operations per iteration, it remains slightly faster - // than a regular loop. - - Debug.Assert(Unsafe.SizeOf() < Unsafe.SizeOf()); - Debug.Assert(Unsafe.SizeOf() < Unsafe.SizeOf()); - - if (IsSafeAccumulator(length)) - { - return LongSum(ref searchSpace, length); - } - else if (IsSafeAccumulator(length)) - { - return LongSum(ref searchSpace, length); - } - else - { - return LongSum(ref searchSpace, length); - } + return sum; } - /// - /// Computes the sum of the values in the specified memory block. - /// - /// The type of elements in the source span. - /// The type of the result. - /// The reference to the start of the memory block. - /// The length of the memory block. - /// The sum of the values in the memory block. - /// The addition operation in a checked context resulted in an overflow. - private static TResult LongSum(ref TSource searchSpace, int length) - where TSource : INumberBase - where TResult : INumberBase + /// + [CLSCompliant(false)] + public static ulong LongSum(this scoped ReadOnlySpan span) { - if (typeof(TSource) == typeof(TResult)) - return (TResult)(object)Sum(ref searchSpace, length); - - if (typeof(TSource) == typeof(byte) && typeof(TResult) == typeof(ulong)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(sbyte) && typeof(TResult) == typeof(long)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(short) && typeof(TResult) == typeof(long)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(ushort) && typeof(TResult) == typeof(ulong)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(int) && typeof(TResult) == typeof(long)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(uint) && typeof(TResult) == typeof(ulong)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(TSource) == typeof(nint) && typeof(TResult) == typeof(long)) - return (TResult)(object)(Unsafe.SizeOf() == sizeof(int) ? LongSum>(ref Unsafe.As(ref searchSpace), length) : (long)Sum>(ref Unsafe.As(ref searchSpace), length)); - - if (typeof(TSource) == typeof(nuint) && typeof(TResult) == typeof(ulong)) - return (TResult)(object)(Unsafe.SizeOf() == sizeof(uint) ? LongSum>(ref Unsafe.As(ref searchSpace), length) : (ulong)Sum>(ref Unsafe.As(ref searchSpace), length)); - - if (typeof(TSource) == typeof(float) && typeof(TResult) == typeof(double)) - return (TResult)(object)LongSum>(ref Unsafe.As(ref searchSpace), length); - - TResult sum = TResult.Zero; - nint end = (nint)length; - - for (nint i = 0; i < end; i++) - sum = checked(sum + TResult.CreateChecked(Unsafe.Add(ref searchSpace, i))); + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; return sum; } - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// The sum of the values in the span. - [CLSCompliant(false)] - public static ulong LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static ulong LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); - /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the span. [CLSCompliant(false)] - public static long LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped Span span) + { + long sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// [CLSCompliant(false)] - public static long LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped ReadOnlySpan span) + { + long sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the span. - public static long LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped Span span) + { + long sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// - public static long LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped ReadOnlySpan span) + { + long sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// /// Computes the sum of a span of values. @@ -314,21 +122,35 @@ private static TResult LongSum(ref TSource searchSpace, int le /// A span of values to calculate the sum of. /// The sum of the values in the span. [CLSCompliant(false)] - public static ulong LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped Span span) + { + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// [CLSCompliant(false)] - public static ulong LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped ReadOnlySpan span) + { + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the span. - public static long LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped Span span) => LongSum(ref MemoryMarshal.GetReference(span), span.Length); /// - public static long LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped ReadOnlySpan span) => LongSum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -336,11 +158,25 @@ private static TResult LongSum(ref TSource searchSpace, int le /// A span of values to calculate the sum of. /// The sum of the values in the span. [CLSCompliant(false)] - public static ulong LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped Span span) + { + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// [CLSCompliant(false)] - public static ulong LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped ReadOnlySpan span) + { + ulong sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// /// Computes the sum of a span of values. @@ -348,10 +184,10 @@ private static TResult LongSum(ref TSource searchSpace, int le /// A span of values to calculate the sum of. /// The sum of the values in the span. /// The addition operation in a checked context resulted in an overflow. - public static long LongSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped Span span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static long LongSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long LongSum(this scoped ReadOnlySpan span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -359,570 +195,53 @@ private static TResult LongSum(ref TSource searchSpace, int le /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static ulong LongSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static ulong LongSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - public static long LongSum(this scoped Span span) => Unsafe.SizeOf() == sizeof(int) ? LongSum>(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length) : (long)Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static long LongSum(this scoped ReadOnlySpan span) => Unsafe.SizeOf() == sizeof(int) ? LongSum>(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length) : (long)Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - [CLSCompliant(false)] - public static ulong LongSum(this scoped Span span) => Unsafe.SizeOf() == sizeof(uint) ? LongSum>(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length) : (ulong)Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static ulong LongSum(this scoped ReadOnlySpan span) => Unsafe.SizeOf() == sizeof(uint) ? LongSum>(ref Unsafe.As(ref MemoryMarshal.GetReference(span)), span.Length) : (ulong)Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong LongSum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the span. - public static double LongSum(this scoped Span span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double LongSum(this scoped Span span) + { + double sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// - public static double LongSum(this scoped ReadOnlySpan span) => LongSum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double LongSum(this scoped ReadOnlySpan span) + { + double sum = 0; + for (int i = 0; i < span.Length; i++) + sum += span[i]; + + return sum; + } /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the span. - public static double LongSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double LongSum(this scoped Span span) => Sum, DoubleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static double LongSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double LongSum(this scoped ReadOnlySpan span) => Sum, DoubleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static decimal LongSum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); + public static decimal LongSum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// - public static decimal LongSum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of generic values. - /// - /// The type of elements in the source span. - /// The type of the result. - /// A span of generic values to calculate the sum of. - /// - public static TResult LongSum(this scoped Span span) where TSource : INumberBase where TResult : INumberBase => LongSum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static TResult LongSum(this scoped ReadOnlySpan span) where TSource : INumberBase where TResult : INumberBase => LongSum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Determines if the specified accumulator type can be safely used - /// to process the data of the given length. - /// - /// The type of the values. - /// The type used for intermediate accumulation. - /// The length of the data to be accumulated. - /// - /// true if the specified accumulator type can be safely used; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeAccumulator(int length) - where TSource : struct, IBinaryNumber, IMinMaxValue - where TAccumulator : struct, IBinaryNumber, IMinMaxValue - { - int vectorCount; - if (!Vector128.IsHardwareAccelerated || length < Vector128.Count) - { - vectorCount = 1; - } - else if (!Vector256.IsHardwareAccelerated || length < Vector256.Count) - { - vectorCount = Vector128.Count; - } - else if (!Vector512.IsHardwareAccelerated || length < Vector512.Count) - { - vectorCount = Vector256.Count; - } - else - { - vectorCount = Vector512.Count; - } - - TAccumulator perLaneLength = TAccumulator.CreateChecked(length / vectorCount); - TAccumulator maxPerLaneLength = TAccumulator.MaxValue / TAccumulator.CreateChecked(TSource.MaxValue); - return perLaneLength <= maxPerLaneLength; - } - - /// - /// Defines a mechanism for computing the sum of two vectors. - /// - /// The element type of the left operand vector. - /// The element type of the right operand vector. - /// The element type of the result vector. - private interface IVectorAddition - { - /// - /// Adds two vectors to compute their sum. - /// - /// The vector to add with . - /// The vector to add with . - /// The sum of and . - static abstract Vector128 Add(Vector128 left, Vector128 right); - - /// - static abstract Vector256 Add(Vector256 left, Vector256 right); - - /// - static abstract Vector512 Add(Vector512 left, Vector512 right); - } - - /// - /// Defines a mechanism for computing the sum of all elements in a vector. - /// - /// The type of the elements in the source vector. - /// The type of the elements in the result vector. - private interface IVectorSummation - { - /// - /// Computes the sum of all elements in a vector. - /// - /// The vector whose elements will be summed. - /// The sum of all elements in vector. - static abstract TResult Sum(Vector128 vector); - - /// - static abstract TResult Sum(Vector256 vector); - - /// - static abstract TResult Sum(Vector512 vector); - } - - /// - /// Defines a mechanism for computing the sum of two vectors. - /// - /// The type of the elements in the vector. - private readonly struct VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) => left + right; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) => left + right; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) => left + right; - } - - /// - /// Defines a mechanism for computing the sum of all elements in a vector. - /// - /// The type of the elements in the vector. - private readonly struct VectorSummation : IVectorSummation - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Sum(Vector128 vector) => Vector128.Sum(vector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Sum(Vector256 vector) => Vector256.Sum(vector); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Sum(Vector512 vector) => Vector512.Sum(vector); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt16ByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } - - /// - /// Defines a mechanism for computing sum of - /// elements in a vector. - /// - private readonly struct UInt16UInt64VectorSummation : IVectorSummation - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector128 vector) - => Vector128.Sum(Vector128.WidenLower(vector) + Vector128.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector256 vector) - => Vector256.Sum(Vector256.WidenLower(vector) + Vector256.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector512 vector) - => Vector512.Sum(Vector512.WidenLower(vector) + Vector512.WidenUpper(vector)); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt32ByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightUInt16 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - return left + Vector128.WidenLower(rightUInt16) + Vector128.WidenUpper(rightUInt16); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightUInt16 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - return left + Vector256.WidenLower(rightUInt16) + Vector256.WidenUpper(rightUInt16); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightUInt16 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - return left + Vector512.WidenLower(rightUInt16) + Vector512.WidenUpper(rightUInt16); - } - } - - /// - /// Defines a mechanism for computing sum of - /// elements in a vector. - /// - private readonly struct UInt32UInt64VectorSummation : IVectorSummation - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector128 vector) - => Vector128.Sum(Vector128.WidenLower(vector) + Vector128.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector256 vector) - => Vector256.Sum(Vector256.WidenLower(vector) + Vector256.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Sum(Vector512 vector) - => Vector512.Sum(Vector512.WidenLower(vector) + Vector512.WidenUpper(vector)); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt64ByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightUInt16 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - Vector128 rightUInt32 = Vector128.WidenLower(rightUInt16) + Vector128.WidenUpper(rightUInt16); - return left + Vector128.WidenLower(rightUInt32) + Vector128.WidenUpper(rightUInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightUInt16 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - Vector256 rightUInt32 = Vector256.WidenLower(rightUInt16) + Vector256.WidenUpper(rightUInt16); - return left + Vector256.WidenLower(rightUInt32) + Vector256.WidenUpper(rightUInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightUInt16 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - Vector512 rightUInt32 = Vector512.WidenLower(rightUInt16) + Vector512.WidenUpper(rightUInt16); - return left + Vector512.WidenLower(rightUInt32) + Vector512.WidenUpper(rightUInt32); - } - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int16SByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } - - /// - /// Defines a mechanism for computing sum of - /// elements in a vector. - /// - private readonly struct Int16Int64VectorSummation : IVectorSummation - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector128 vector) - => Vector128.Sum(Vector128.WidenLower(vector) + Vector128.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector256 vector) - => Vector256.Sum(Vector256.WidenLower(vector) + Vector256.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector512 vector) - => Vector512.Sum(Vector512.WidenLower(vector) + Vector512.WidenUpper(vector)); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int32SByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightInt16 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - return left + Vector128.WidenLower(rightInt16) + Vector128.WidenUpper(rightInt16); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightInt16 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - return left + Vector256.WidenLower(rightInt16) + Vector256.WidenUpper(rightInt16); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightInt16 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - return left + Vector512.WidenLower(rightInt16) + Vector512.WidenUpper(rightInt16); - } - } - - /// - /// Defines a mechanism for computing sum of - /// elements in a vector. - /// - private readonly struct Int32Int64VectorSummation : IVectorSummation - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector128 vector) - => Vector128.Sum(Vector128.WidenLower(vector) + Vector128.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector256 vector) - => Vector256.Sum(Vector256.WidenLower(vector) + Vector256.WidenUpper(vector)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Sum(Vector512 vector) - => Vector512.Sum(Vector512.WidenLower(vector) + Vector512.WidenUpper(vector)); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int64SByteVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightInt16 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - Vector128 rightInt32 = Vector128.WidenLower(rightInt16) + Vector128.WidenUpper(rightInt16); - return left + Vector128.WidenLower(rightInt32) + Vector128.WidenUpper(rightInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightInt16 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - Vector256 rightInt32 = Vector256.WidenLower(rightInt16) + Vector256.WidenUpper(rightInt16); - return left + Vector256.WidenLower(rightInt32) + Vector256.WidenUpper(rightInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightInt16 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - Vector512 rightInt32 = Vector512.WidenLower(rightInt16) + Vector512.WidenUpper(rightInt16); - return left + Vector512.WidenLower(rightInt32) + Vector512.WidenUpper(rightInt32); - } - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int32Int16VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int64Int16VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightInt32 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - return left + Vector128.WidenLower(rightInt32) + Vector128.WidenUpper(rightInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightInt32 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - return left + Vector256.WidenLower(rightInt32) + Vector256.WidenUpper(rightInt32); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightInt32 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - return left + Vector512.WidenLower(rightInt32) + Vector512.WidenUpper(rightInt32); - } - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt32UInt16VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt64UInt16VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - { - Vector128 rightUInt32 = Vector128.WidenLower(right) + Vector128.WidenUpper(right); - return left + (Vector128.WidenLower(rightUInt32) + Vector128.WidenUpper(rightUInt32)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - { - Vector256 rightUInt32 = Vector256.WidenLower(right) + Vector256.WidenUpper(right); - return left + (Vector256.WidenLower(rightUInt32) + Vector256.WidenUpper(rightUInt32)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - { - Vector512 rightUInt32 = Vector512.WidenLower(right) + Vector512.WidenUpper(right); - return left + (Vector512.WidenLower(rightUInt32) + Vector512.WidenUpper(rightUInt32)); - } - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct Int64Int32VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct UInt64UInt32VectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + (Vector128.WidenLower(right) + Vector128.WidenUpper(right)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + (Vector256.WidenLower(right) + Vector256.WidenUpper(right)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + (Vector512.WidenLower(right) + Vector512.WidenUpper(right)); - } - - /// - /// Defines a mechanism for computing the sum of and - /// vectors. - /// - private readonly struct DoubleSingleVectorAddition : IVectorAddition - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Add(Vector128 left, Vector128 right) - => left + Vector128.WidenLower(right) + Vector128.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Add(Vector256 left, Vector256 right) - => left + Vector256.WidenLower(right) + Vector256.WidenUpper(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Add(Vector512 left, Vector512 right) - => left + Vector512.WidenLower(right) + Vector512.WidenUpper(right); - } + public static decimal LongSum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); } diff --git a/src/Spanned/Spans.Max.cs b/src/Spanned/Spans.Max.cs index 3691dfd..e93deb5 100644 --- a/src/Spanned/Spans.Max.cs +++ b/src/Spanned/Spans.Max.cs @@ -2,218 +2,6 @@ namespace Spanned; public static partial class Spans { - /// - /// Performs a vectorized search for the maximum value in a memory block that contains IEEE 754 floats. - /// - /// The IEEE 754 floating-point type. - /// - /// The unsigned integer type can be interpreted as. - /// - /// The reference to the start of the search space. - /// The length of the search space. - /// The maximum value in the memory block. - /// or is not supported. - /// contains no elements. - private static TFloat MaxFloat(ref TFloat searchSpace, int length) - where TFloat : struct, IFloatingPointIeee754 - where TInteger : struct, IUnsignedNumber - { - // Unlike with `MinFloat` (refer to the comment on that method for more info), - // even if the input is poisoned by NaNs, we still need to return a meaningful - // value most of the time. - // Merely identifying NaN's presence in the given memory block is not enough, - // as it only informs us that the comparison logic was probably compromised - // somewhere down the line. - // Thus, in this case, we need to ensure that NaN cannot break the comparison. - // - // To achieve this, we must find a non-NaN value in the input (any NaN value - // is considered to be less than any non-NaN value). If there's no such value, - // return NaN, indicating that the sequence only contains NaNs. Otherwise, we - // can safely filter out NaN values from the input, replacing them with the - // found valid value to ensure the deterministic nature of comparison operations. - // - // To detect NaNs in a vector, we can use the same trick as in `MinFloat`: - // any NaN, treated as an unsigned integer with its most significant bit set to 1, - // is guaranteed to be greater than any other value represented the same way. - // Also, it's guaranteed to be at least 1 greater than negative infinity casted to its - // unsigned integer form. - // - // Unfortunately, this method is slower than `MinFloat`. While it still benefits from - // vectorization, the situation would be in a much better shape without the need to deal - // with the NaN concept every iteration. So, there are a few things we could consider: - // - // 1) Leave everything as is. In this case, every scenario (i.e., whether the input - // contains NaNs or not) will see slight performance improvements. - // 2) Use a slightly tweaked `MinFloat` implementation to search for a maximum value, - // but instead of returning a NaN value upon its detection, reset the search state - // and fallback to a regular loop. This way, inputs that do not contain NaNs will - // see huge performance improvements, while those that do contain at least a single - // NaN will see a 15-20% performance degradation. Is it worth it? In the end of the - // day, NaN is a result of a floating-point exception, and exceptions are not designed - // to be fast and efficient. How often do you see a NaN value present in a valid input? - // 3) Almost the same as (2), but instead of using a regular loop as our fallback, the - // current `MaxFloat` implementation could be used instead. This way, valid inputs will - // once again see huge performance improvements, while inputs that contain NaNs, which - // are processed twice but with vectorized solutions that are at least 2x faster than - // a naive loop, will see little to no performance improvements and definitely should not - // see any degradations. Seems like a perfect solution if we don't need to think about - // assembly size. Is it worth it to keep a huge, chunky method that only deals with - // NaN-containing inputs? Once again, we need a definitive answer on how often one can - // observe a NaN value in a valid input. And without any statistics on this matter, your - // guess is as good as mine. Therefore, while I would love to choose one of (2) and (3), - // at the moment I need to go with (1), as it's the safest option out of all three. - - Debug.Assert(Vector.IsSupported && Vector.IsSupported); - Debug.Assert(Unsafe.SizeOf() == Unsafe.SizeOf()); - - if (length == 0) - ThrowHelper.ThrowInvalidOperationException_NoElements(); - - ref TFloat current = ref searchSpace; - ref TFloat end = ref Unsafe.Add(ref current, length); - - // Find a non-NaN value. - TFloat nonNaN; - do - { - nonNaN = current; - current = ref Unsafe.Add(ref current, (nint)1); - } - while (TFloat.IsNaN(nonNaN) && Unsafe.IsAddressLessThan(ref current, ref end)); - - if (Unsafe.AreSame(ref current, ref end)) - return nonNaN; - - // Note, we use `<=` instead of `<`, because in the end we need to - // manually process every lane of the resulting vector. - // Therefore, when `length == Vector.Count` a vectorized solution - // would just end up performing unnecessary operations, becoming - // slower than the regular loop without those. - if (!Vector128.IsHardwareAccelerated || length <= Vector128.Count) - { - TFloat maxValue = nonNaN; - do - { - // Once we've found a value that is not NaN, we don't need to worry - // about NaN weirdness anymore. This is because `NaN > maxValue` is always - // false, and it mimics the desired outcome in this case, similar to - // the situation where `!(minValue > maxValue)`. - if (current > maxValue) - maxValue = current; - - current = ref Unsafe.Add(ref current, (nint)1); - } - while (Unsafe.IsAddressLessThan(ref current, ref end)); - - return maxValue; - } - else if (!Vector256.IsHardwareAccelerated || length <= Vector256.Count) - { - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., 1 << 63 for 64 bits. - Vector128 sign = Vector128.One << (Unsafe.SizeOf() * 8 - 1); - - // (*(TInteger*)&NaN | sign) > *(TInteger*)&NegativeInfinity - Vector128 limit = Vector128.Create(Unsafe.BitCast(TFloat.NegativeInfinity)); - - // Fill the initial vector with non-NaN values present in the search space. - Vector128 max = Vector128.Create(nonNaN); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - Vector128 vector = Vector128.LoadUnsafe(ref current); - Vector128 isNaN = Vector128.GreaterThan(vector.As() | sign, limit).As(); - - max = Vector128.Max(max, Vector128.ConditionalSelect(isNaN, max, vector)); - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); - } - Vector128 lastVector = Vector128.LoadUnsafe(ref lastVectorStart); - Vector128 lastIsNaN = Vector128.GreaterThan(lastVector.As() | sign, limit).As(); - max = Vector128.Max(max, Vector128.ConditionalSelect(lastIsNaN, max, lastVector)); - - // `max[0]` is guaranteed to be non-NaN. - // We filtered out those pesky NaNs earlier. - TFloat maxValue = max[0]; - for (int i = 1; i < Vector128.Count; i++) - { - if (max[i] > maxValue) - maxValue = max[i]; - } - return maxValue; - } - else if (!Vector512.IsHardwareAccelerated || length <= Vector512.Count) - { - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., 1 << 63 for 64 bits. - Vector256 sign = Vector256.One << (Unsafe.SizeOf() * 8 - 1); - - // (*(TInteger*)&NaN | sign) > *(TInteger*)&NegativeInfinity - Vector256 limit = Vector256.Create(Unsafe.BitCast(TFloat.NegativeInfinity)); - - // Fill the initial vector with non-NaN values present in the search space. - Vector256 max = Vector256.Create(nonNaN); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - Vector256 vector = Vector256.LoadUnsafe(ref current); - Vector256 isNaN = Vector256.GreaterThan(vector.As() | sign, limit).As(); - - max = Vector256.Max(max, Vector256.ConditionalSelect(isNaN, max, vector)); - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - } - Vector256 lastVector = Vector256.LoadUnsafe(ref lastVectorStart); - Vector256 lastIsNaN = Vector256.GreaterThan(lastVector.As() | sign, limit).As(); - max = Vector256.Max(max, Vector256.ConditionalSelect(lastIsNaN, max, lastVector)); - - // `max[0]` is guaranteed to be non-NaN. - // We filtered out those pesky NaNs earlier. - TFloat maxValue = max[0]; - for (int i = 1; i < Vector256.Count; i++) - { - if (max[i] > maxValue) - maxValue = max[i]; - } - return maxValue; - } - else - { - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector512.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., 1 << 63 for 64 bits. - Vector512 sign = Vector512.One << (Unsafe.SizeOf() * 8 - 1); - - // (*(TInteger*)&NaN | sign) > *(TInteger*)&NegativeInfinity - Vector512 limit = Vector512.Create(Unsafe.BitCast(TFloat.NegativeInfinity)); - - // Fill the initial vector with non-NaN values present in the search space. - Vector512 max = Vector512.Create(nonNaN); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - Vector512 vector = Vector512.LoadUnsafe(ref current); - Vector512 isNaN = Vector512.GreaterThan(vector.As() | sign, limit).As(); - - max = Vector512.Max(max, Vector512.ConditionalSelect(isNaN, max, vector)); - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); - } - Vector512 lastVector = Vector512.LoadUnsafe(ref lastVectorStart); - Vector512 lastIsNaN = Vector512.GreaterThan(lastVector.As() | sign, limit).As(); - max = Vector512.Max(max, Vector512.ConditionalSelect(lastIsNaN, max, lastVector)); - - // `max[0]` is guaranteed to be non-NaN. - // We filtered out those pesky NaNs earlier. - TFloat maxValue = max[0]; - for (int i = 1; i < Vector512.Count; i++) - { - if (max[i] > maxValue) - maxValue = max[i]; - } - return maxValue; - } - } - /// /// Returns the maximum value in a memory block. /// @@ -259,18 +47,6 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len if (typeof(T) == typeof(ulong) && comparer is null) return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - if (typeof(T) == typeof(nint) && comparer is null) - return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nuint) && comparer is null) - return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(float) && comparer is null) - return (T)(object)MaxFloat(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(double) && comparer is null) - return (T)(object)MaxFloat(ref Unsafe.As(ref searchSpace), length); - if (length == 0) { if (default(T) is null) @@ -279,9 +55,7 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len ThrowHelper.ThrowInvalidOperationException_NoElements(); } - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. - nint i = 1; - nint end = (nint)length; + int i = 1; T maxValue = searchSpace; // Instead of `typeof(T).IsValueType`, test for reference types and @@ -291,7 +65,7 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len { if (comparer is null) { - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); @@ -302,7 +76,7 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len } else { - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); if (comparer.Compare(currentValue, maxValue) > 0) @@ -314,10 +88,10 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len { comparer ??= Comparer.Default; - for (; maxValue is null && i < end; i++) + for (; maxValue is null && i < length; i++) maxValue = Unsafe.Add(ref searchSpace, i); - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); if (currentValue is not null && comparer.Compare(currentValue, maxValue) > 0) @@ -358,38 +132,30 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len /// of type . /// /// The type of values to compare. - private readonly struct Maximum : IExtremum where T : struct, IBinaryNumber + private readonly struct Maximum : IExtremum where T : struct, IComparable { /// - /// Compares two values to determine which is greater. + /// Compares two values to determine which is less. /// /// The value to compare with . /// The value to compare with . /// - /// true if is greater than ; + /// true if is less than ; /// otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Compare(T left, T right) => left > right; + public bool Compare(T left, T right) => left.CompareTo(right) > 0; /// - /// Computes the maximum of two vectors on a per-element basis. + /// Computes the minimum of two vectors on a per-element basis. /// /// The vector to compare with . /// The vector to compare with . /// - /// A vector whose elements are the maximum of the corresponding elements in left and right. + /// A vector whose elements are the minimum of the corresponding elements in left and right. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Compare(Vector128 left, Vector128 right) => Vector128.Max(left, right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Compare(Vector256 left, Vector256 right) => Vector256.Max(left, right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Compare(Vector512 left, Vector512 right) => Vector512.Max(left, right); + public Vector Compare(Vector left, Vector right) => Vector.Max(left, right); } /// @@ -481,47 +247,25 @@ private static TFloat MaxFloat(ref TFloat searchSpace, int len [CLSCompliant(false)] public static ulong Max(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - /// - /// Returns the maximum value in a span of values. - /// - /// A span of values to determine the maximum value of. - /// - public static nint Max(this scoped Span span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static nint Max(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Returns the maximum value in a span of values. - /// - /// A span of values to determine the maximum value of. - /// - [CLSCompliant(false)] - public static nuint Max(this scoped Span span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static nuint Max(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - /// /// Returns the maximum value in a span of values. /// /// A span of values to determine the maximum value of. /// - public static float Max(this scoped Span span) => MaxFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static float Max(this scoped Span span) => Max(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// - public static float Max(this scoped ReadOnlySpan span) => MaxFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static float Max(this scoped ReadOnlySpan span) => Max(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// /// Returns the maximum value in a span of values. /// /// A span of values to determine the maximum value of. /// - public static double Max(this scoped Span span) => MaxFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static double Max(this scoped Span span) => Max(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// - public static double Max(this scoped ReadOnlySpan span) => MaxFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static double Max(this scoped ReadOnlySpan span) => Max(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// /// Returns the maximum value in a span of values. diff --git a/src/Spanned/Spans.Min.cs b/src/Spanned/Spans.Min.cs index f82563f..6a382ae 100644 --- a/src/Spanned/Spans.Min.cs +++ b/src/Spanned/Spans.Min.cs @@ -2,259 +2,6 @@ namespace Spanned; public static partial class Spans { - /// - /// Performs a vectorized search for the minimum value in a memory block that contains IEEE 754 floats. - /// - /// The IEEE 754 floating-point type. - /// - /// The unsigned integer type can be interpreted as. - /// - /// The reference to the start of the search space. - /// The length of the search space. - /// The minimum value in the memory block. - /// or is not supported. - /// contains no elements. - private static TFloat MinFloat(ref TFloat searchSpace, int length) - where TFloat : struct, IFloatingPointIeee754 - where TInteger : struct, IUnsignedNumber - { - // We need to special case IEEE 754 floats due to the NaN concept, which, I'm sure, - // we all appreciate dearly. Since NaN != NaN, NaN != x, !(NaN < x), !(NaN > x), - // it's impossible to rely on the regular comparison rules while processing - // a sequence that may contain a result of a floating point exception. - // This is the reason .NET doesn't vectorize Min/Max operations on float inputs. - // - // However, we can define Min for floats as `Contains(x, NaN) ? NaN : Min(x)`, - // since in case the input does not contain NaNs, comparison rules are perfectly - // predictable. Also, we do not really care about the order of the operations - - // in case the search space contains a NaN, we discard the result of Min() anyways, - // whether it's valid or not; otherwise, its result is **guaranteed** to be valid. - // Therefore, it's possible to search for the minimum value and for a NaN - // simultaneously in a given sequence. - // - // The next problem we face is actually searching for a NaN because it is not - // a single value; it's actually a range of values. According to IEEE 754, any - // value that follows the following format is considered a NaN: - // - sign = 0 or 1. - // - exponent = all 1 bits. - // - mantissa = at least one 1 bit. - // So, double-precision (1 bit sign, 11 bit exponent, 52 bit mantissa) floats - // (which I will be using for my examples, but the logic stays the same for any - // other precision level, including, but not limited to single-precision floats), - // can throw 2^53 - 2 = 9007199254740990 different NaNs at you. Which is... a lot. - // We are definitely not hardcoding that! Let's take a closer look at the binary - // representation of the floats in question and see if there's something interesting. - // - // s (sign); e (exponent); m (mantissa) - // *: seeeeeeeeeeemmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm - // NaN: 0111111111111000000000000000000000000000000000000000000000000000 - // +Inf: 0111111111110000000000000000000000000000000000000000000000000000 - // +Max: 0111111111101111111111111111111111111111111111111111111111111111 - // ...: ???????????0???????????????????????????????????????????????????? - // -Max: 1111111111101111111111111111111111111111111111111111111111111111 - // -Inf: 1111111111110000000000000000000000000000000000000000000000000000 - // NaN: 1111111111111000000000000000000000000000000000000000000000000000 - // - // And something interesting there is indeed. If we treat these values as unsigned - // integers, any NaN that has its sign bit set to 1 is greater than any valid value. - // What will happen if we set the sign bit to 1 for every value? Valid positive floats - // will begin to collide with their negative counterparts, but there is no positive or - // negative NaN; they are all just NaNs. Therefore, logically speaking, a NaN is not - // affected* by such an operation. Thus, any NaN (treated as an unsigned integer) is - // guaranteed to be greater than any valid float value (treated as an unsigned integer) - // in a sequence where every element has its sign bit set to 1. - // - // Knowing all that, we can search for a min float while simultaneously searching for - // a max unsigned integer (ignoring the most significant bit) in the same memory block. - // When we are done, if the maximum unsigned integer, converted back to an IEEE 754 float, - // turns out to be a NaN, we return it instead of the found min float; otherwise, we return - // the min float we have found, since in this case, as stated before, it's guaranteed that - // the input was not poisoned by NaNs. - // - // Following this logic, the search can now be vectorized. - // - // *P.S. - Some runtimes (not the .NET-related ones, though) differentiate between - // qNaN and sNaN. The distinction is based on the mantissa value rather than - // the sign bit. Therefore, we are still okay with the sign bit flipping trick. - - Debug.Assert(Vector.IsSupported && Vector.IsSupported); - Debug.Assert(Unsafe.SizeOf() == Unsafe.SizeOf()); - - if (length == 0) - ThrowHelper.ThrowInvalidOperationException_NoElements(); - - // Note, we use `<=` instead of `<`, because in the end we need to - // manually process every lane of the resulting vector. - // Therefore, when `length == Vector.Count` a vectorized solution - // would just end up performing unnecessary operations, becoming - // slower than the regular loop without those. - if (!Vector128.IsHardwareAccelerated || length <= Vector128.Count) - { - ref TFloat current = ref searchSpace; - ref TFloat end = ref Unsafe.Add(ref searchSpace, length); - - TFloat minValue = current; - current = ref Unsafe.Add(ref current, (nint)1); - - while (Unsafe.IsAddressLessThan(ref current, ref end)) - { - if (current < minValue) - { - minValue = current; - } - else if (TFloat.IsNaN(current)) - { - // Short-circuit, as we define NaN as the smallest value possible. - return current; - } - - current = ref Unsafe.Add(ref current, (nint)1); - } - return minValue; - } - else if (!Vector256.IsHardwareAccelerated || length <= Vector256.Count) - { - ref TFloat current = ref searchSpace; - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., 1 << 63 for 64 bits. - Vector128 sign = Vector128.One << (Unsafe.SizeOf() * 8 - 1); - - Vector128 min = Vector128.LoadUnsafe(ref current); - Vector128 max = min.As() | sign; - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - min = Vector128.Min(min, Vector128.LoadUnsafe(ref current)); - max = Vector128.Max(max, Vector128.LoadUnsafe(ref current).As() | sign); - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); - } - min = Vector128.Min(min, Vector128.LoadUnsafe(ref lastVectorStart)); - max = Vector128.Max(max, Vector128.LoadUnsafe(ref lastVectorStart).As() | sign); - - // Use a "magic value" instead of min[0], so we don't need to introduce - // additional branching for a NaN check, similar to the one contained - // in the following for loop. - // - // No value is greater than positive infinity. Therefore, it is guaranteed - // to be replaced by an actual value from the vector, provided it contains - // anything other than positive infinity. Otherwise, PositiveInfinity - // is already the value we are looking for. - TFloat minValue = TFloat.PositiveInfinity; - - for (int i = 0; i < Vector128.Count; i++) - { - // Do NOT invert this if. - // We want the branch predictor to prefer the non-NaN case. - if (!TFloat.IsNaN(Unsafe.BitCast(max[i]))) - { - if (min[i] < minValue) - minValue = min[i]; - - continue; - } - - return Unsafe.BitCast(max[i]); - } - return minValue; - } - else if (!Vector512.IsHardwareAccelerated || length <= Vector512.Count) - { - ref TFloat current = ref searchSpace; - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., 1 << 63 for 64 bits. - Vector256 sign = Vector256.One << (Unsafe.SizeOf() * 8 - 1); - - Vector256 min = Vector256.LoadUnsafe(ref current); - Vector256 max = min.As() | sign; - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - min = Vector256.Min(min, Vector256.LoadUnsafe(ref current)); - max = Vector256.Max(max, Vector256.LoadUnsafe(ref current).As() | sign); - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - } - min = Vector256.Min(min, Vector256.LoadUnsafe(ref lastVectorStart)); - max = Vector256.Max(max, Vector256.LoadUnsafe(ref lastVectorStart).As() | sign); - - // Use a "magic value" instead of min[0], so we don't need to introduce - // additional branching for a NaN check, similar to the one contained - // in the following for loop. - // - // No value is greater than positive infinity. Therefore, it is guaranteed - // to be replaced by an actual value from the vector, provided it contains - // anything other than positive infinity. Otherwise, PositiveInfinity - // is already the value we are looking for. - TFloat minValue = TFloat.PositiveInfinity; - - for (int i = 0; i < Vector256.Count; i++) - { - // Do NOT invert this if. - // We want the branch predictor to prefer the non-NaN branch. - if (!TFloat.IsNaN(Unsafe.BitCast(max[i]))) - { - if (min[i] < minValue) - minValue = min[i]; - - continue; - } - - return Unsafe.BitCast(max[i]); - } - return minValue; - } - else - { - ref TFloat current = ref searchSpace; - ref TFloat lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector512.Count); - - // Set the most significant bit. 1 << (bit_sizeof(T) - 1), e.g., (1 << 63) for 64 bits. - Vector512 sign = Vector512.One << (Unsafe.SizeOf() * 8 - 1); - - Vector512 min = Vector512.LoadUnsafe(ref current); - Vector512 max = min.As() | sign; - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); - - while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart)) - { - min = Vector512.Min(min, Vector512.LoadUnsafe(ref current)); - max = Vector512.Max(max, Vector512.LoadUnsafe(ref current).As() | sign); - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); - } - min = Vector512.Min(min, Vector512.LoadUnsafe(ref lastVectorStart)); - max = Vector512.Max(max, Vector512.LoadUnsafe(ref lastVectorStart).As() | sign); - - // Use a "magic value" instead of min[0], so we don't need to introduce - // additional branching for a NaN check, similar to the one contained - // in the following for loop. - // - // No value is greater than positive infinity. Therefore, it is guaranteed - // to be replaced by an actual value from the vector, provided it contains - // anything other than positive infinity. Otherwise, PositiveInfinity - // is already the value we are looking for. - TFloat minValue = TFloat.PositiveInfinity; - - for (int i = 0; i < Vector512.Count; i++) - { - // Do NOT invert this if. - // We want the branch predictor to prefer the non-NaN branch. - if (!TFloat.IsNaN(Unsafe.BitCast(max[i]))) - { - if (min[i] < minValue) - minValue = min[i]; - - continue; - } - - return Unsafe.BitCast(max[i]); - } - return minValue; - } - } - /// /// Returns the minimum value in a memory block. /// @@ -300,18 +47,6 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len if (typeof(T) == typeof(ulong) && comparer is null) return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - if (typeof(T) == typeof(nint) && comparer is null) - return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nuint) && comparer is null) - return (T)(object)Extremum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(float) && comparer is null) - return (T)(object)MinFloat(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(double) && comparer is null) - return (T)(object)MinFloat(ref Unsafe.As(ref searchSpace), length); - if (length == 0) { if (default(T) is null) @@ -320,9 +55,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len ThrowHelper.ThrowInvalidOperationException_NoElements(); } - // Use nint for arithmetic to avoid unnecessary 64->32->64 truncations. - nint i = 1; - nint end = (nint)length; + int i = 1; T minValue = searchSpace; // Instead of `typeof(T).IsValueType`, test for reference types and @@ -333,7 +66,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len { if (comparer is null) { - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); @@ -344,7 +77,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len } else { - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); if (comparer.Compare(currentValue, minValue) < 0) @@ -356,10 +89,10 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len { comparer ??= Comparer.Default; - for (; minValue is null && i < end; i++) + for (; minValue is null && i < length; i++) minValue = Unsafe.Add(ref searchSpace, i); - for (; i < end; i++) + for (; i < length; i++) { T currentValue = Unsafe.Add(ref searchSpace, i); if (currentValue is not null && comparer.Compare(currentValue, minValue) < 0) @@ -400,7 +133,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len /// of type . /// /// The type of values to compare. - private readonly struct Minimum : IExtremum where T : struct, IBinaryNumber + private readonly struct Minimum : IExtremum where T : struct, IComparable { /// /// Compares two values to determine which is less. @@ -412,7 +145,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len /// otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Compare(T left, T right) => left < right; + public bool Compare(T left, T right) => left.CompareTo(right) < 0; /// /// Computes the minimum of two vectors on a per-element basis. @@ -423,15 +156,7 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len /// A vector whose elements are the minimum of the corresponding elements in left and right. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Compare(Vector128 left, Vector128 right) => Vector128.Min(left, right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Compare(Vector256 left, Vector256 right) => Vector256.Min(left, right); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Compare(Vector512 left, Vector512 right) => Vector512.Min(left, right); + public Vector Compare(Vector left, Vector right) => Vector.Min(left, right); } /// @@ -523,47 +248,25 @@ private static TFloat MinFloat(ref TFloat searchSpace, int len [CLSCompliant(false)] public static ulong Min(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - /// - /// Returns the minimum value in a span of values. - /// - /// A span of values to determine the minimum value of. - /// - public static nint Min(this scoped Span span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static nint Min(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Returns the minimum value in a span of values. - /// - /// A span of values to determine the minimum value of. - /// - [CLSCompliant(false)] - public static nuint Min(this scoped Span span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static nuint Min(this scoped ReadOnlySpan span) => Extremum>(ref MemoryMarshal.GetReference(span), span.Length); - /// /// Returns the minimum value in a span of values. /// /// A span of values to determine the minimum value of. /// - public static float Min(this scoped Span span) => MinFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static float Min(this scoped Span span) => Min(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// - public static float Min(this scoped ReadOnlySpan span) => MinFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static float Min(this scoped ReadOnlySpan span) => Min(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// /// Returns the minimum value in a span of values. /// /// A span of values to determine the minimum value of. /// - public static double Min(this scoped Span span) => MinFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static double Min(this scoped Span span) => Min(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// - public static double Min(this scoped ReadOnlySpan span) => MinFloat(ref MemoryMarshal.GetReference(span), span.Length); + public static double Min(this scoped ReadOnlySpan span) => Min(ref MemoryMarshal.GetReference(span), span.Length, comparer: null); /// /// Returns the minimum value in a span of values. diff --git a/src/Spanned/Spans.Sort.cs b/src/Spanned/Spans.Sort.cs new file mode 100644 index 0000000..ff2f51e --- /dev/null +++ b/src/Spanned/Spans.Sort.cs @@ -0,0 +1,90 @@ +namespace Spanned; + +public static partial class Spans +{ + internal static void Sort(this Span span, Comparison comparison) + { + ThrowHelper.ThrowArgumentNullException_IfNull(comparison); + + if (span.Length < 2) + return; + + QuickSort(span, comparison, 0, span.Length - 1); + } + + internal static void Sort(this Span span, IComparer? comparer = null) + { + if (span.Length < 2) + return; + + QuickSort(span, comparer ?? Comparer.Default, 0, span.Length - 1); + } + + private static void QuickSort(Span span, IComparer comparer, int leftIndex, int rightIndex) + { + int i = leftIndex; + int j = rightIndex; + T pivot = span[leftIndex]; + + while (i <= j) + { + while (comparer.Compare(span[i], pivot) < 0) + { + i++; + } + + while (comparer.Compare(span[j], pivot) > 0) + { + j--; + } + + if (i <= j) + { + (span[j], span[i]) = (span[i], span[j]); + + i++; + j--; + } + } + + if (leftIndex < j) + QuickSort(span, comparer, leftIndex, j); + + if (i < rightIndex) + QuickSort(span, comparer, i, rightIndex); + } + + private static void QuickSort(Span span, Comparison comparison, int leftIndex, int rightIndex) + { + int i = leftIndex; + int j = rightIndex; + T pivot = span[leftIndex]; + + while (i <= j) + { + while (comparison(span[i], pivot) < 0) + { + i++; + } + + while (comparison(span[j], pivot) > 0) + { + j--; + } + + if (i <= j) + { + (span[j], span[i]) = (span[i], span[j]); + + i++; + j--; + } + } + + if (leftIndex < j) + QuickSort(span, comparison, leftIndex, j); + + if (i < rightIndex) + QuickSort(span, comparison, i, rightIndex); + } +} diff --git a/src/Spanned/Spans.Sum.cs b/src/Spanned/Spans.Sum.cs index f015739..4a419f2 100644 --- a/src/Spanned/Spans.Sum.cs +++ b/src/Spanned/Spans.Sum.cs @@ -7,125 +7,69 @@ public static partial class Spans /// /// The type of the values. /// The type for tracking overflow in vectorized arithmetic operations. + /// The type of the values. /// The reference to the start of the memory block. /// The length of the memory block. /// The sum of the values in the memory block. /// is not supported. /// The addition operation in a checked context resulted in an overflow. - private static T Sum(ref T searchSpace, int length) - where T : struct, IBinaryNumber, IMinMaxValue - where TOverflowTracker : IOverflowTracker + private static T Sum(ref T searchSpace, int length) + where T : struct + where TOverflowTracker : struct, IOverflowTracker + where TNumber : struct, INumber { - Debug.Assert(Vector.IsSupported); - ref T current = ref searchSpace; ref T end = ref Unsafe.Add(ref current, length); - T sum = T.Zero; + T sum = default; // Note, we use `<=` instead of `<`, because in the end we need to // manually process every lane of the resulting vector. // Therefore, when `length == Vector.Count` a vectorized solution // would just end up performing unnecessary operations, becoming // slower than the regular loop without those. - if (!Vector128.IsHardwareAccelerated || length <= Vector128.Count) - { - // The operation cannot be vectorized. - // Skip to the manual loop. - } - else if (!Vector256.IsHardwareAccelerated || length <= Vector256.Count) + if (Vector.IsHardwareAccelerated && length > Vector.Count) { - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector128.Count); - Vector128 overflowTrackerState = Vector128.Zero; - Vector128 sums = Vector128.Zero; + ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector.Count); + Vector overflowTrackerState = Vector.Zero; + Vector sums = Vector.Zero; do { - Vector128 currentVector = Vector128.LoadUnsafe(ref current); - Vector128 newSums = sums + currentVector; - overflowTrackerState = TOverflowTracker.UpdateState(overflowTrackerState, sums, currentVector, newSums); + Vector currentVector = new(MemoryMarshal.CreateSpan(ref current, Vector.Count)); + Vector newSums = sums + currentVector; + overflowTrackerState = default(TOverflowTracker).UpdateState(overflowTrackerState, sums, currentVector, newSums); sums = newSums; - current = ref Unsafe.Add(ref current, (nint)Vector128.Count); + current = ref Unsafe.Add(ref current, (nint)Vector.Count); } while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - if (TOverflowTracker.HasOverflow(overflowTrackerState)) + if (default(TOverflowTracker).HasOverflow(overflowTrackerState)) ThrowHelper.ThrowOverflowException(); - if (TOverflowTracker.IsSupported) + if (default(TOverflowTracker).IsSupported) { - for (int i = 0; i < Vector128.Count; i++) - sum = checked(sum + sums[i]); + for (int i = 0; i < Vector.Count; i++) + sum = default(TNumber).AddChecked(sum, sums[i]); } else { - sum = Vector128.Sum(sums); + for (int i = 0; i < Vector.Count; i++) + sum = default(TNumber).Add(sum, sums[i]); } } - else if (!Vector512.IsHardwareAccelerated || length <= Vector512.Count) - { - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector256.Count); - Vector256 overflowTrackerState = Vector256.Zero; - Vector256 sums = Vector256.Zero; - - do - { - Vector256 currentVector = Vector256.LoadUnsafe(ref current); - Vector256 newSums = sums + currentVector; - overflowTrackerState = TOverflowTracker.UpdateState(overflowTrackerState, sums, currentVector, newSums); - - sums = newSums; - current = ref Unsafe.Add(ref current, (nint)Vector256.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - if (TOverflowTracker.HasOverflow(overflowTrackerState)) - ThrowHelper.ThrowOverflowException(); - - if (TOverflowTracker.IsSupported) - { - for (int i = 0; i < Vector256.Count; i++) - sum = checked(sum + sums[i]); - } - else - { - sum = Vector256.Sum(sums); - } + if (default(TOverflowTracker).IsSupported) + { + for (; Unsafe.IsAddressLessThan(ref current, ref end); current = ref Unsafe.Add(ref current, (nint)1)) + sum = default(TNumber).AddChecked(sum, current); } else { - ref T lastVectorStart = ref Unsafe.Add(ref searchSpace, length - Vector512.Count); - Vector512 overflowTrackerState = Vector512.Zero; - Vector512 sums = Vector512.Zero; - - do - { - Vector512 currentVector = Vector512.LoadUnsafe(ref current); - Vector512 newSums = sums + currentVector; - overflowTrackerState = TOverflowTracker.UpdateState(overflowTrackerState, sums, currentVector, newSums); - - sums = newSums; - current = ref Unsafe.Add(ref current, (nint)Vector512.Count); - } - while (!Unsafe.IsAddressGreaterThan(ref current, ref lastVectorStart)); - - if (TOverflowTracker.HasOverflow(overflowTrackerState)) - ThrowHelper.ThrowOverflowException(); - - if (TOverflowTracker.IsSupported) - { - for (int i = 0; i < Vector512.Count; i++) - sum = checked(sum + sums[i]); - } - else - { - sum = Vector512.Sum(sums); - } + for (; Unsafe.IsAddressLessThan(ref current, ref end); current = ref Unsafe.Add(ref current, (nint)1)) + sum = default(TNumber).Add(sum, current); } - for (; Unsafe.IsAddressLessThan(ref current, ref end); current = ref Unsafe.Add(ref current, (nint)1)) - sum = TOverflowTracker.IsSupported ? checked(sum + current) : (sum + current); - return sum; } @@ -133,54 +77,18 @@ private static T Sum(ref T searchSpace, int length) /// Computes the sum of the values in the specified memory block. /// /// The type of the values. + /// The type of the values. /// The reference to the start of the memory block. /// The length of the memory block. /// The sum of the values in the memory block. /// The addition operation in a checked context resulted in an overflow. - private static T Sum(ref T searchSpace, int length) - where T : INumberBase + private static T Sum(ref T searchSpace, int length) + where T : struct + where TNumber : struct, INumber { - if (typeof(T) == typeof(byte)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(sbyte)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(short)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(ushort)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(int)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(uint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(long)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(ulong)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nuint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(float)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(double)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - T sum = T.Zero; - nint end = (nint)length; - - for (nint i = 0; i < end; i++) - sum = checked(sum + Unsafe.Add(ref searchSpace, i)); + T sum = default; + for (int i = 0; i < length; i++) + sum = default(TNumber).AddChecked(sum, Unsafe.Add(ref searchSpace, i)); return sum; } @@ -193,53 +101,17 @@ private static T Sum(ref T searchSpace, int length) /// Use with caution and ensure that values in the span cannot cause an overflow if it is not desirable. /// /// The type of the values. + /// The type of the values. /// The reference to the start of the memory block. /// The length of the memory block. /// The sum of the values in the memory block. - private static T UnsafeSum(ref T searchSpace, int length) - where T : INumberBase + private static T UnsafeSum(ref T searchSpace, int length) + where T : struct + where TNumber : struct, INumber { - if (typeof(T) == typeof(byte)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(sbyte)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(short)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(ushort)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(int)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(uint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(long)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(ulong)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(nuint)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(float)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - if (typeof(T) == typeof(double)) - return (T)(object)Sum>(ref Unsafe.As(ref searchSpace), length); - - T sum = T.Zero; - nint end = (nint)length; - - for (nint i = 0; i < end; i++) - sum += Unsafe.Add(ref searchSpace, i); + T sum = default; + for (int i = 0; i < length; i++) + sum = default(TNumber).Add(sum, Unsafe.Add(ref searchSpace, i)); return sum; } @@ -249,12 +121,12 @@ private static T UnsafeSum(ref T searchSpace, int length) /// /// The numeric type. private interface IOverflowTracker - where T : struct, INumberBase, IMinMaxValue + where T : struct { /// /// Gets a value indicating whether overflow tracking is supported for the specified numeric type. /// - static abstract bool IsSupported { get; } + bool IsSupported { get; } /// /// Determines whether the overflow has occurred. @@ -263,13 +135,7 @@ private interface IOverflowTracker /// /// true if the overflow has occurred; otherwise, false. /// - static abstract bool HasOverflow(Vector128 state); - - /// - static abstract bool HasOverflow(Vector256 state); - - /// - static abstract bool HasOverflow(Vector512 state); + bool HasOverflow(Vector state); /// /// Updates the overflow tracking state. @@ -279,13 +145,7 @@ private interface IOverflowTracker /// The right operand of the vector operation. /// The result of the vector operation. /// The updated overflow tracking state. - static abstract Vector128 UpdateState(Vector128 state, Vector128 leftOperand, Vector128 rightOperand, Vector128 result); - - /// - static abstract Vector256 UpdateState(Vector256 state, Vector256 leftOperand, Vector256 rightOperand, Vector256 result); - - /// - static abstract Vector512 UpdateState(Vector512 state, Vector512 leftOperand, Vector512 rightOperand, Vector512 result); + Vector UpdateState(Vector state, Vector leftOperand, Vector rightOperand, Vector result); } /// @@ -293,107 +153,43 @@ private interface IOverflowTracker /// /// The numeric type. private readonly struct NullOverflowTracker : IOverflowTracker - where T : struct, INumberBase, IMinMaxValue + where T : struct { - public static bool IsSupported + public bool IsSupported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector128 state) => false; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector256 state) => false; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector512 state) => false; + public bool HasOverflow(Vector state) => false; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UpdateState(Vector128 state, Vector128 leftOperand, Vector128 rightOperand, Vector128 result) => state; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UpdateState(Vector256 state, Vector256 leftOperand, Vector256 rightOperand, Vector256 result) => state; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 UpdateState(Vector512 state, Vector512 leftOperand, Vector512 rightOperand, Vector512 result) => state; + public Vector UpdateState(Vector state, Vector leftOperand, Vector rightOperand, Vector result) => state; } /// /// Represents an overflow tracker for signed integer types. /// /// The numeric type. - private readonly struct SignedIntegerOverflowTracker : IOverflowTracker - where T : struct, IBinaryInteger, ISignedNumber, IMinMaxValue + /// The numeric type. + private readonly struct SignedIntegerOverflowTracker : IOverflowTracker + where T : struct + where TNumber : struct, INumber { - public static bool IsSupported + public bool IsSupported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector128 state) - => (state & Vector128.Create(T.MinValue)) != Vector128.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector256 state) - => (state & Vector256.Create(T.MinValue)) != Vector256.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector512 state) - => (state & Vector512.Create(T.MinValue)) != Vector512.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UpdateState(Vector128 state, Vector128 leftOperand, Vector128 rightOperand, Vector128 result) - => state | ((result ^ leftOperand) & (result ^ rightOperand)); + public bool HasOverflow(Vector state) + => (state & new Vector(default(TNumber).MinValue)) != Vector.Zero; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UpdateState(Vector256 state, Vector256 leftOperand, Vector256 rightOperand, Vector256 result) + public Vector UpdateState(Vector state, Vector leftOperand, Vector rightOperand, Vector result) => state | ((result ^ leftOperand) & (result ^ rightOperand)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 UpdateState(Vector512 state, Vector512 leftOperand, Vector512 rightOperand, Vector512 result) - => state | ((result ^ leftOperand) & (result ^ rightOperand)); - } - - /// - /// Represents an overflow tracker for unsigned integer types. - /// - /// The numeric type. - private readonly struct UnsignedIntegerOverflowTracker : IOverflowTracker - where T : struct, IBinaryInteger, IUnsignedNumber, IMinMaxValue - { - public static bool IsSupported - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector128 state) - => (state & Vector128.Create((T.MaxValue >> 1) + T.One)) != Vector128.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector256 state) - => (state & Vector256.Create((T.MaxValue >> 1) + T.One)) != Vector256.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasOverflow(Vector512 state) - => (state & Vector512.Create((T.MaxValue >> 1) + T.One)) != Vector512.Zero; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 UpdateState(Vector128 state, Vector128 leftOperand, Vector128 rightOperand, Vector128 result) - => state | ((leftOperand & rightOperand) | Vector128.AndNot(leftOperand | rightOperand, result)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 UpdateState(Vector256 state, Vector256 leftOperand, Vector256 rightOperand, Vector256 result) - => state | ((leftOperand & rightOperand) | Vector256.AndNot(leftOperand | rightOperand, result)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 UpdateState(Vector512 state, Vector512 leftOperand, Vector512 rightOperand, Vector512 result) - => state | ((leftOperand & rightOperand) | Vector512.AndNot(leftOperand | rightOperand, result)); } /// @@ -402,10 +198,10 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// The sum of the values in the span. /// The addition operation in a checked context resulted in an overflow. - public static byte Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static byte Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// - public static byte Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static byte Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -413,21 +209,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static sbyte Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static sbyte Sum(this scoped Span span) => Sum, SByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static sbyte Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static sbyte Sum(this scoped ReadOnlySpan span) => Sum, SByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static short Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static short Sum(this scoped Span span) => Sum, Int16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static short Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static short Sum(this scoped ReadOnlySpan span) => Sum, Int16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -435,21 +231,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static ushort Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ushort Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static ushort Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ushort Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static int Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static int Sum(this scoped Span span) => Sum, Int32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static int Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static int Sum(this scoped ReadOnlySpan span) => Sum, Int32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -457,21 +253,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static uint Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static uint Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static uint Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static uint Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static long Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long Sum(this scoped Span span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static long Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long Sum(this scoped ReadOnlySpan span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -479,74 +275,41 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static ulong Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static ulong Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - public static nint Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static nint Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - [CLSCompliant(false)] - public static nuint Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static nuint Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the sequence. - public static float Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static float Sum(this scoped Span span) => Sum, SingleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static float Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static float Sum(this scoped ReadOnlySpan span) => Sum, SingleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// The sum of the values in the sequence. - public static double Sum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double Sum(this scoped Span span) => Sum, DoubleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static double Sum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static double Sum(this scoped ReadOnlySpan span) => Sum, DoubleNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static decimal Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); + public static decimal Sum(this scoped Span span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// - public static decimal Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of generic values. - /// - /// The type of the span. - /// A span of generic values to calculate the sum of. - /// - public static T Sum(this scoped Span span) where T : INumberBase => Sum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static T Sum(this scoped ReadOnlySpan span) where T : INumberBase => Sum(ref MemoryMarshal.GetReference(span), span.Length); + public static decimal Sum(this scoped ReadOnlySpan span) => Sum(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -557,10 +320,10 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// /// A span of values to calculate the sum of. /// The sum of the values in the span. - public static byte UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static byte UnsafeSum(this scoped Span span) => Sum, ByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static byte UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static byte UnsafeSum(this scoped ReadOnlySpan span) => Sum, ByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -568,21 +331,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static sbyte UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static sbyte UnsafeSum(this scoped Span span) => Sum, SByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static sbyte UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static sbyte UnsafeSum(this scoped ReadOnlySpan span) => Sum, SByteNumber>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static short UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static short UnsafeSum(this scoped Span span) => Sum, Int16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static short UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static short UnsafeSum(this scoped ReadOnlySpan span) => Sum, Int16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -590,21 +353,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static ushort UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ushort UnsafeSum(this scoped Span span) => Sum, UInt16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static ushort UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ushort UnsafeSum(this scoped ReadOnlySpan span) => Sum, UInt16Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static int UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static int UnsafeSum(this scoped Span span) => Sum, Int32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static int UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static int UnsafeSum(this scoped ReadOnlySpan span) => Sum, Int32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -612,21 +375,21 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static uint UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static uint UnsafeSum(this scoped Span span) => Sum, UInt32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static uint UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static uint UnsafeSum(this scoped ReadOnlySpan span) => Sum, UInt32Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. /// /// A span of values to calculate the sum of. /// - public static long UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long UnsafeSum(this scoped Span span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// - public static long UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static long UnsafeSum(this scoped ReadOnlySpan span) => Sum, Int64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// /// Computes the sum of a span of values. @@ -634,42 +397,9 @@ public static Vector512 UpdateState(Vector512 state, Vector512 leftOper /// A span of values to calculate the sum of. /// [CLSCompliant(false)] - public static ulong UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong UnsafeSum(this scoped Span span) => Sum, UInt64Number>(ref MemoryMarshal.GetReference(span), span.Length); /// [CLSCompliant(false)] - public static ulong UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - public static nint UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static nint UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of values. - /// - /// A span of values to calculate the sum of. - /// - [CLSCompliant(false)] - public static nuint UnsafeSum(this scoped Span span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - [CLSCompliant(false)] - public static nuint UnsafeSum(this scoped ReadOnlySpan span) => Sum>(ref MemoryMarshal.GetReference(span), span.Length); - - /// - /// Computes the sum of a span of generic values. - /// - /// The type of the span. - /// A span of generic values to calculate the sum of. - /// - public static T UnsafeSum(this scoped Span span) where T : INumberBase => UnsafeSum(ref MemoryMarshal.GetReference(span), span.Length); - - /// - public static T UnsafeSum(this scoped ReadOnlySpan span) where T : INumberBase => UnsafeSum(ref MemoryMarshal.GetReference(span), span.Length); + public static ulong UnsafeSum(this scoped ReadOnlySpan span) => Sum, UInt64Number>(ref MemoryMarshal.GetReference(span), span.Length); } diff --git a/src/Spanned/Spans.cs b/src/Spanned/Spans.cs index 6127106..9d89d05 100644 --- a/src/Spanned/Spans.cs +++ b/src/Spanned/Spans.cs @@ -28,12 +28,6 @@ public static bool TryGetSpan(this IEnumerable? enumerable, out ReadOnlySp return true; } - if (enumerable.GetType() == typeof(List)) - { - span = AsSpan((List)enumerable); - return true; - } - if (typeof(T) == typeof(char) && enumerable is string str) { span = UnsafeCast(str); @@ -45,50 +39,6 @@ public static bool TryGetSpan(this IEnumerable? enumerable, out ReadOnlySp return false; } - /// - /// Converts a to a . - /// - /// The type of the elements in the list. - /// The list to convert to a span. - /// A representing the elements in the list. - public static Span AsSpan(this List? list) - => CollectionsMarshal.AsSpan(list); - - /// - /// Retrieves a representing the remaining capacity of a . - /// - /// The type of the elements in the list. - /// The list to obtain the capacity span from. - /// A representing the remaining capacity of the list. - public static Span AsRemainingSpan(this List? list) - { - if (list is not null) - { - ref T firstElement = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(list)); - ref T afterLastElement = ref Unsafe.Add(ref firstElement, list.Count); - return MemoryMarshal.CreateSpan(ref afterLastElement, list.Capacity - list.Count); - } - - return default; - } - - /// - /// Retrieves a representing the entire capacity of a . - /// - /// The type of the elements in the list. - /// The list to obtain the capacity span from. - /// A representing the entire capacity of the list. - public static Span AsCapacitySpan(this List? list) - { - if (list is not null) - { - ref T firstElement = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(list)); - return MemoryMarshal.CreateSpan(ref firstElement, list.Capacity); - } - - return default; - } - /// /// Copies elements of a to a new . /// @@ -108,8 +58,8 @@ public static List ToList(this scoped ReadOnlySpan span) { List list = new(span.Length); - CollectionsMarshal.SetCount(list, span.Length); - span.CopyTo(CollectionsMarshal.AsSpan(list)); + for (int i = 0; i < span.Length; i++) + list.Add(span[i]); return list; } diff --git a/src/Spanned/Text/ValueStringBuilder.Append.cs b/src/Spanned/Text/ValueStringBuilder.Append.cs index 42f7dfd..5dfd01b 100644 --- a/src/Spanned/Text/ValueStringBuilder.Append.cs +++ b/src/Spanned/Text/ValueStringBuilder.Append.cs @@ -96,7 +96,7 @@ private void AppendString(string value) Grow(value.Length); } - value.CopyTo(_buffer.Slice(length)); + value.AsSpan().CopyTo(_buffer.Slice(length)); _length = length + value.Length; } @@ -186,7 +186,7 @@ private unsafe void Append(in char value, int valueCount) Grow(valueCount); } - MemoryMarshal.CreateReadOnlySpan(in value, valueCount).CopyTo(_buffer.Slice(length)); + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in value), valueCount).CopyTo(_buffer.Slice(length)); _length = length + valueCount; } @@ -260,7 +260,12 @@ public void Append(char[]? value, int start, int length) /// /// The object to append. public void Append(object? value) - => Append(value); + { + if (value is not null) + { + Append(value.ToString()); + } + } /// /// Appends the string representation of a specified value to this instance. @@ -537,66 +542,6 @@ public void Append(decimal value) } } - /// - /// Appends the string representation of a specified object to this instance. - /// - /// The type of the value to append. - /// The object to append. - public void Append(T? value) - { - if (typeof(T).IsEnum) - { - // Stephen Toub, https://github.com/dotnet/runtime/issues/57881#issuecomment-903248743: - // These were added as instance TryFormat methods, implementing the ISpanFormattable interface. - // Enum could do that as well, but without assistance from the JIT it'll incur some of the same overheads - // you're seeing in your STR_Parse_text benchmark: those allocations are for boxing the enum - // in order to call ToString (for the known enum values, ToString itself won't allocate). - // - // Stephen Toub, https://github.com/dotnet/runtime/pull/78580#issue-1455927685: - // And then that Enum.TryFormat is used in multiple interpolated string handlers to avoid boxing the enum; - // today it'll be boxed as part of using its IFormattable implementation, and with this change, - // it both won’t be boxed and will call through the optimized generic path. This uses typeof(T).IsEnum, - // which is now a JIT intrinsic that'll become a const true/false. - // - // Basically, we special-case enums, even though they [explicitly] implement ISpanFormattable, to avoid boxing. - // Sadly, we cannot use `Enum.TryFormat` directly since there's currently no way to "bridge" generic constraints. - // Thankfully, we now have the `UnsafeAccessorAttribute`, so we can call its unconstrained internal version. - if (EnumHelper.TryFormatUnconstrained(value, _buffer.Slice(_length), out int charsWritten)) - { - _length += charsWritten; - } - else - { - FormatAndAppend(value, format: null, provider: null); - } - } - else if (value is ISpanFormattable) - { - Span destination = _buffer.Slice(_length); - if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, format: default, provider: null)) - { - // Protect against faulty ISpanFormattable implementations. - // We don't want to destabilize a structure that might operate on data allocated on the stack. - if ((uint)charsWritten > (uint)destination.Length) - { - ThrowHelper.ThrowFormatException_InvalidString(); - } - - _length += charsWritten; - } - else - { - // ValueStringBuilder doesn't have enough space for the current value. - // Therefore, we first format it separately into a temporary buffer, and then append it to the builder. - FormatAndAppend(value, format: null, provider: null); - } - } - else if (value is not null) - { - Append(value.ToString()); - } - } - /// /// Appends a formatted representation of the specified value to this instance. /// @@ -624,57 +569,7 @@ public void Append(T? value, string? format = null, IFormatProvider? provider // Only if they implement both we end up paying for an extra interface check. if (value is IFormattable) { - if (typeof(T).IsEnum) - { - // Stephen Toub, https://github.com/dotnet/runtime/issues/57881#issuecomment-903248743: - // These were added as instance TryFormat methods, implementing the ISpanFormattable interface. - // Enum could do that as well, but without assistance from the JIT it'll incur some of the same overheads - // you're seeing in your STR_Parse_text benchmark: those allocations are for boxing the enum - // in order to call ToString (for the known enum values, ToString itself won't allocate). - // - // Stephen Toub, https://github.com/dotnet/runtime/pull/78580#issue-1455927685: - // And then that Enum.TryFormat is used in multiple interpolated string handlers to avoid boxing the enum; - // today it'll be boxed as part of using its IFormattable implementation, and with this change, - // it both won’t be boxed and will call through the optimized generic path. This uses typeof(T).IsEnum, - // which is now a JIT intrinsic that'll become a const true/false. - // - // Basically, we special-case enums, even though they [explicitly] implement ISpanFormattable, to avoid boxing. - // Sadly, we cannot use `Enum.TryFormat` directly since there's currently no way to "bridge" generic constraints. - // Thankfully, we now have the `UnsafeAccessorAttribute`, so we can call its unconstrained internal version. - if (EnumHelper.TryFormatUnconstrained(value, _buffer.Slice(_length), out int charsWritten, format)) - { - _length += charsWritten; - } - else - { - FormatAndAppend(value, format, provider); - } - } - else if (value is ISpanFormattable) - { - Span destination = _buffer.Slice(_length); - if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, format, provider)) - { - // Protect against faulty ISpanFormattable implementations. - // We don't want to destabilize a structure that might operate on data allocated on the stack. - if ((uint)charsWritten > (uint)destination.Length) - { - ThrowHelper.ThrowFormatException_InvalidString(); - } - - _length += charsWritten; - } - else - { - // ValueStringBuilder doesn't have enough space for the current value. - // Therefore, we first format it separately into a temporary buffer, and then append it to the builder. - FormatAndAppend(value, format, provider); - } - } - else - { - Append(((IFormattable)value).ToString(format, provider)); - } + Append(((IFormattable)value).ToString(format, provider)); } else if (value is not null) { @@ -692,31 +587,10 @@ public void Append(T? value, string? format = null, IFormatProvider? provider /// /// This method does not support custom formatters. /// - [SkipLocalsInit] // 256 * sizeof(char) == 0.5 KiB, that's a lot to init for nothing. private void FormatAndAppend(T? value, string? format, IFormatProvider? provider) { - Span chars = stackalloc char[StringHelper.StackallocCharBufferSizeLimit]; - int charsWritten = 0; - if (value is IFormattable) { - if (typeof(T).IsEnum) - { - if (EnumHelper.TryFormatUnconstrained(value, chars, out charsWritten, format)) - { - Append(chars.Slice(0, charsWritten)); - return; - } - } - else if (value is ISpanFormattable) - { - if (((ISpanFormattable)value).TryFormat(chars, out charsWritten, format, provider)) - { - Append(chars.Slice(0, charsWritten)); - return; - } - } - Append(((IFormattable)value).ToString(format, provider)); } else if (value is not null) diff --git a/src/Spanned/Text/ValueStringBuilder.AppendFormat.cs b/src/Spanned/Text/ValueStringBuilder.AppendFormat.cs index 805c8e4..7b57fce 100644 --- a/src/Spanned/Text/ValueStringBuilder.AppendFormat.cs +++ b/src/Spanned/Text/ValueStringBuilder.AppendFormat.cs @@ -1,171 +1,9 @@ -using System.Text; using Spanned.Collections.Generic; namespace Spanned.Text; public ref partial struct ValueStringBuilder { - /// - /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. - /// Each format item is replaced by the string representation of any of the arguments using a specified format provider. - /// - /// The type of the first object to format. - /// An object that supplies culture-specific formatting information. - /// A . - /// The first object to format. - /// is null. - /// The index of a format item is greater than or equal to the number of supplied arguments. - public void AppendFormat(IFormatProvider? provider, CompositeFormat format, TArg0 arg0) - { - ThrowHelper.ThrowArgumentNullException_IfNull(format); - - format.ValidateNumberOfArgs(1); - AppendFormatCore(provider, format, arg0, 0, 0, default); - } - - /// - /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. - /// Each format item is replaced by the string representation of any of the arguments using a specified format provider. - /// - /// The type of the first object to format. - /// The type of the second object to format. - /// An object that supplies culture-specific formatting information. - /// A . - /// The first object to format. - /// The second object to format. - /// is null. - /// The index of a format item is greater than or equal to the number of supplied arguments. - public void AppendFormat(IFormatProvider? provider, CompositeFormat format, TArg0 arg0, TArg1 arg1) - { - ThrowHelper.ThrowArgumentNullException_IfNull(format); - - format.ValidateNumberOfArgs(2); - AppendFormatCore(provider, format, arg0, arg1, 0, default); - } - - /// - /// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. - /// Each format item is replaced by the string representation of any of the arguments using a specified format provider. - /// - /// The type of the first object to format. - /// The type of the second object to format. - /// The type of the third object to format. - /// An object that supplies culture-specific formatting information. - /// A . - /// The first object to format. - /// The second object to format. - /// The third object to format. - /// is null. - /// The index of a format item is greater than or equal to the number of supplied arguments. - public void AppendFormat(IFormatProvider? provider, CompositeFormat format, TArg0 arg0, TArg1 arg1, TArg2 arg2) - { - ThrowHelper.ThrowArgumentNullException_IfNull(format); - - format.ValidateNumberOfArgs(3); - AppendFormatCore(provider, format, arg0, arg1, arg2, default); - } - - /// - /// Appends the string returned by processing a composite format string, which contains - /// zero or more format items, to this instance. Each format item is replaced by - /// the string representation of any of the arguments using a specified format provider. - /// - /// An object that supplies culture-specific formatting information. - /// A . - /// A span of objects to format. - /// or is null. - /// The index of a format item is greater than or equal to the number of supplied arguments. - public void AppendFormat(IFormatProvider? provider, CompositeFormat format, params object?[] args) - { - ThrowHelper.ThrowArgumentNullException_IfNull(args); - - AppendFormat(provider, format, (ReadOnlySpan)args); - } - - /// - /// Appends the string returned by processing a composite format string, which contains - /// zero or more format items, to this instance. Each format item is replaced by - /// the string representation of any of the arguments using a specified format provider. - /// - /// An object that supplies culture-specific formatting information. - /// A . - /// A span of objects to format. - /// is null. - /// The index of a format item is greater than or equal to the number of supplied arguments. - public void AppendFormat(IFormatProvider? provider, CompositeFormat format, scoped ReadOnlySpan args) - { - ThrowHelper.ThrowArgumentNullException_IfNull(format); - - format.ValidateNumberOfArgs(args.Length); - switch (args.Length) - { - case 0: - AppendFormatCore(provider, format, (object?)null, (object?)null, (object?)null, args); - return; - - case 1: - AppendFormatCore(provider, format, args[0], (object?)null, (object?)null, args); - return; - - case 2: - AppendFormatCore(provider, format, args[0], args[1], (object?)null, args); - return; - - default: - AppendFormatCore(provider, format, args[0], args[1], args[2], args); - return; - } - } - - /// - /// Appends the string returned by processing a composite format string, which contains - /// zero or more format items, to this instance. Each format item is replaced by - /// the string representation of any of the arguments using a specified format provider. - /// - /// The type of the first object to format. - /// The type of the second object to format. - /// The type of the third object to format. - /// An object that supplies culture-specific formatting information. - /// A . - /// The first object to format. - /// The second object to format. - /// The third object to format. - /// A span of objects to format. - private void AppendFormatCore(IFormatProvider? provider, CompositeFormat format, TArg0 arg0, TArg1 arg1, TArg2 arg2, scoped ReadOnlySpan args) - { - AppendInterpolatedStringHandler handler = new(format.LiteralLength(), format.FormattedCount(), this, provider); - - foreach ((string? literal, int argIndex, int argAlignment, string? argFormat) in format.Segments()) - { - if (literal is not null) - { - handler.AppendLiteral(literal); - continue; - } - - switch (argIndex) - { - case 0: - handler.AppendFormatted(arg0, argAlignment, argFormat); - break; - - case 1: - handler.AppendFormatted(arg1, argAlignment, argFormat); - break; - - case 2: - handler.AppendFormatted(arg2, argAlignment, argFormat); - break; - - default: - handler.AppendFormatted(args[argIndex], argAlignment, argFormat); - break; - } - } - - this = handler._builder; - } - /// /// Appends the string returned by processing a composite format string, which contains /// zero or more format items, to this instance. Each format item is replaced by @@ -467,7 +305,7 @@ private void AppendFormatCore(IFormatProvider? provider, string format, scoped R if (ch != '}') { // Continue consuming optional additional digits. - while (char.IsAsciiDigit(ch) && index < IndexLimit) + while (IsAsciiDigit(ch) && index < IndexLimit) { index = index * 10 + ch - '0'; ch = MoveNext(format, ref pos); @@ -508,7 +346,7 @@ private void AppendFormatCore(IFormatProvider? provider, string format, scoped R ThrowHelper.ThrowFormatException_ExpectedAsciiDigit(); } ch = MoveNext(format, ref pos); - while (char.IsAsciiDigit(ch) && width < WidthLimit) + while (IsAsciiDigit(ch) && width < WidthLimit) { width = width * 10 + ch - '0'; ch = MoveNext(format, ref pos); @@ -580,34 +418,8 @@ private void AppendFormatCore(IFormatProvider? provider, string format, scoped R if (s == null) { - // If arg is ISpanFormattable and the beginning doesn't need padding, - // try formatting it into the remaining current chunk. Span destination = _buffer.Slice(_length); - if ((leftJustify || width == 0) && - arg is ISpanFormattable spanFormattableArg && - spanFormattableArg.TryFormat(destination, out int charsWritten, itemFormatSpan, provider)) - { - if ((uint)charsWritten > (uint)destination.Length) - { - // Untrusted ISpanFormattable implementations might return an erroneous charsWritten value, - // and m_ChunkLength might end up being used in Unsafe code, so fail if we get back an - // out-of-range charsWritten value. - ThrowHelper.ThrowFormatException_InvalidString(); - } - _length += charsWritten; - - // Pad the end, if needed. - if (leftJustify && width > charsWritten) - { - Append(' ', width - charsWritten); - } - - // Continue to parse other characters. - continue; - } - - // Otherwise, fallback to trying IFormattable or calling ToString. if (arg is IFormattable formattableArg) { if (itemFormatSpan.Length != 0) @@ -643,6 +455,8 @@ arg is ISpanFormattable spanFormattableArg && // Continue parsing the rest of the format string. } + static bool IsAsciiDigit(char c) => (uint)(c - '0') <= 9u; + [MethodImpl(MethodImplOptions.AggressiveInlining)] static char MoveNext(string format, ref int pos) { diff --git a/src/Spanned/Text/ValueStringBuilder.AppendInterpolatedStringHandler.cs b/src/Spanned/Text/ValueStringBuilder.AppendInterpolatedStringHandler.cs deleted file mode 100644 index 73efb28..0000000 --- a/src/Spanned/Text/ValueStringBuilder.AppendInterpolatedStringHandler.cs +++ /dev/null @@ -1,470 +0,0 @@ -#pragma warning disable IDE0060 // The "unused" parameters are required by the compiler. - -using System.Globalization; - -namespace Spanned.Text; - -public ref partial struct ValueStringBuilder -{ - /// - public void Append([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) - { - // Sadly, it's not possible to pass the builder to a handler instance by its reference. - // So, its value will be **copied**, causing de-syncs in cases when any of the operations - // on handler causes a copied version of the builder to grow its internal buffer. - // To prevent de-syncs, just copy all the updated fields back to this instance when the handler is done. - this = handler._builder; - } - - /// - /// Appends the specified interpolated string to this instance. - /// - /// An object that supplies culture-specific formatting information. - /// The interpolated string to append. - public void Append(IFormatProvider? provider, [InterpolatedStringHandlerArgument("", nameof(provider))] ref AppendInterpolatedStringHandler handler) - { - // Sadly, it's not possible to pass the builder to a handler instance by its reference. - // So, its value will be **copied**, causing de-syncs in cases when any of the operations - // on handler causes a copied version of the builder to grow its internal buffer. - // To prevent de-syncs, just copy all the updated fields back to this instance when the handler is done. - this = handler._builder; - } - - /// - public void AppendLine([InterpolatedStringHandlerArgument("")] ref AppendInterpolatedStringHandler handler) - { - // Sadly, it's not possible to pass the builder to a handler instance by its reference. - // So, its value will be **copied**, causing de-syncs in cases when any of the operations - // on handler causes a copied version of the builder to grow its internal buffer. - // To prevent de-syncs, just copy all the updated fields back to this instance when the handler is done. - this = handler._builder; - - Append(Environment.NewLine); - } - - /// - /// Appends the specified interpolated string followed by the default line terminator to the end of the current instance. - /// - /// An object that supplies culture-specific formatting information. - /// The interpolated string to append. - public void AppendLine(IFormatProvider? provider, [InterpolatedStringHandlerArgument("", nameof(provider))] ref AppendInterpolatedStringHandler handler) - { - // Sadly, it's not possible to pass the builder to a handler instance by its reference. - // So, its value will be **copied**, causing de-syncs in cases when any of the operations - // on handler causes a copied version of the builder to grow its internal buffer. - // To prevent de-syncs, just copy all the updated fields back to this instance when the handler is done. - this = handler._builder; - - Append(Environment.NewLine); - } - - /// - /// A handler used by the language compiler to append interpolated strings to instances. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [InterpolatedStringHandler] - public ref struct AppendInterpolatedStringHandler - { - /// - /// The associated to which to append. - /// - internal ValueStringBuilder _builder; - - /// - /// The custom format provider used for the calls. - /// - private readonly IFormatProvider? _provider; - - /// - /// Indicates whether provides an . - /// - /// - /// Custom formatters are very rare. Therefore, it doesn't make sense to create a reference type field - /// for that in a ref struct, which should be as compact as possible. - /// - private readonly bool _hasCustomFormatter; - - /// - public AppendInterpolatedStringHandler(int literalLength, int formattedCount, ValueStringBuilder builder) - { - _builder = builder; - _provider = null; - _hasCustomFormatter = false; - } - - /// - /// Initializes a new instance. - /// - /// The number of constant characters outside of interpolation expressions in the interpolated string. - /// The number of interpolation expressions in the interpolated string. - /// The associated to which to append. - /// An object that supplies culture-specific formatting information. - /// - /// This is constructor is intended to be called only by the compiler-generated code. - /// Therefore, arguments are not validated. - /// - public AppendInterpolatedStringHandler(int literalLength, int formattedCount, ValueStringBuilder builder, IFormatProvider? provider) - { - _builder = builder; - _provider = provider; - _hasCustomFormatter = provider is not (null or CultureInfo) && provider.GetFormat(typeof(ICustomFormatter)) is not null; - } - - /// - /// Appends a literal string to the handler. - /// - /// The literal string value to append. - public void AppendLiteral(string value) => _builder.Append(value); - - /// - public void AppendFormatted(T value) - { - // While any sane person would just delegate the call to another overload with a `null` format, - // explicitly passing default as the format to TryFormat can improve code quality in certain cases when TryFormat is inlined. - // For instance, with Int32, it enables the JIT to eliminate code in the inlined method based on a length check on the format. - // - // Thus, duplicating the entire method yields significant performance benefits in this scenario, - // which outweigh the concerns regarding code maintainability. - - // If there's a custom formatter, let it deal with the formatting - if (_hasCustomFormatter) - { - AppendCustomFormatted(value, alignment: 0, format: null); - return; - } - - // First, check for `IFormattable`, even though `ISpanFormattable` is preferred. The latter - // requires the former anyway. - // - // For value types, it won't matter since the type checks become JIT-time constants. - // - // For reference types, it's more probable they implement `IFormattable` over `ISpanFormattable`. - // So, if they don't implement either, we save an interface check over first checking for `ISpanFormattable` - // and then for `IFormattable`, and if they only implement `IFormattable`, we come out even. - // Only if they implement both we end up paying for an extra interface check. - if (value is IFormattable) - { - if (typeof(T).IsEnum) - { - // Stephen Toub, https://github.com/dotnet/runtime/issues/57881#issuecomment-903248743: - // These were added as instance TryFormat methods, implementing the ISpanFormattable interface. - // Enum could do that as well, but without assistance from the JIT it'll incur some of the same overheads - // you're seeing in your STR_Parse_text benchmark: those allocations are for boxing the enum - // in order to call ToString (for the known enum values, ToString itself won't allocate). - // - // Stephen Toub, https://github.com/dotnet/runtime/pull/78580#issue-1455927685: - // And then that Enum.TryFormat is used in multiple interpolated string handlers to avoid boxing the enum; - // today it'll be boxed as part of using its IFormattable implementation, and with this change, - // it both won’t be boxed and will call through the optimized generic path. This uses typeof(T).IsEnum, - // which is now a JIT intrinsic that'll become a const true/false. - // - // Basically, we special-case enums, even though they [explicitly] implement ISpanFormattable, to avoid boxing. - // Sadly, we cannot use `Enum.TryFormat` directly since there's currently no way to "bridge" generic constraints. - // Thankfully, we now have the `UnsafeAccessorAttribute`, so we can call its unconstrained internal version. - if (EnumHelper.TryFormatUnconstrained(value, _builder.AsRemainingSpan(), out int charsWritten)) - { - _builder._length += charsWritten; - } - else - { - FormatAndAppend(value, alignment: 0, format: null); - } - } - else if (value is ISpanFormattable) - { - Span destination = _builder.AsRemainingSpan(); - if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, default, _provider)) - { - // Protect against faulty ISpanFormattable implementations. - // We don't want to destabilize a structure that might operate on data allocated on the stack. - if ((uint)charsWritten > (uint)destination.Length) - { - ThrowHelper.ThrowFormatException_InvalidString(); - } - - _builder._length += charsWritten; - } - else - { - // ValueStringBuilder doesn't have enough space for the current value. - // Therefore, we first format it separately into a temporary buffer, and then append it to the builder. - FormatAndAppend(value, 0, format: null); - } - } - else - { - _builder.Append(((IFormattable)value).ToString(format: null, _provider)); - } - } - else if (value is not null) - { - _builder.Append(value.ToString()); - } - } - - /// - public void AppendFormatted(T value, string? format) - { - // If there's a custom formatter, let it deal with the formatting. - if (_hasCustomFormatter) - { - AppendCustomFormatted(value, alignment: 0, format); - return; - } - - // First, check for `IFormattable`, even though `ISpanFormattable` is preferred. The latter - // requires the former anyway. - // - // For value types, it won't matter since the type checks become JIT-time constants. - // - // For reference types, it's more probable they implement `IFormattable` over `ISpanFormattable`. - // So, if they don't implement either, we save an interface check over first checking for `ISpanFormattable` - // and then for `IFormattable`, and if they only implement `IFormattable`, we come out even. - // Only if they implement both we end up paying for an extra interface check. - if (value is IFormattable) - { - if (typeof(T).IsEnum) - { - // Stephen Toub, https://github.com/dotnet/runtime/issues/57881#issuecomment-903248743: - // These were added as instance TryFormat methods, implementing the ISpanFormattable interface. - // Enum could do that as well, but without assistance from the JIT it'll incur some of the same overheads - // you're seeing in your STR_Parse_text benchmark: those allocations are for boxing the enum - // in order to call ToString (for the known enum values, ToString itself won't allocate). - // - // Stephen Toub, https://github.com/dotnet/runtime/pull/78580#issue-1455927685: - // And then that Enum.TryFormat is used in multiple interpolated string handlers to avoid boxing the enum; - // today it'll be boxed as part of using its IFormattable implementation, and with this change, - // it both won’t be boxed and will call through the optimized generic path. This uses typeof(T).IsEnum, - // which is now a JIT intrinsic that'll become a const true/false. - // - // Basically, we special-case enums, even though they [explicitly] implement ISpanFormattable, to avoid boxing. - // Sadly, we cannot use `Enum.TryFormat` directly since there's currently no way to "bridge" generic constraints. - // Thankfully, we now have the `UnsafeAccessorAttribute`, so we can call its unconstrained internal version. - if (EnumHelper.TryFormatUnconstrained(value, _builder.AsRemainingSpan(), out int charsWritten, format)) - { - _builder._length += charsWritten; - } - else - { - FormatAndAppend(value, alignment: 0, format); - } - } - else if (value is ISpanFormattable) - { - Span destination = _builder.AsRemainingSpan(); - if (((ISpanFormattable)value).TryFormat(destination, out int charsWritten, format, _provider)) - { - // Protect against faulty ISpanFormattable implementations. - // We don't want to destabilize a structure that might operate on data allocated on the stack. - if ((uint)charsWritten > (uint)destination.Length) - { - ThrowHelper.ThrowFormatException_InvalidString(); - } - - _builder._length += charsWritten; - } - else - { - // ValueStringBuilder doesn't have enough space for the current value. - // Therefore, we first format it separately into a temporary buffer, and then append it to the builder. - FormatAndAppend(value, 0, format); - } - } - else - { - _builder.Append(((IFormattable)value).ToString(format, _provider)); - } - } - else if (value is not null) - { - _builder.Append(value.ToString()); - } - } - - /// - public void AppendFormatted(T value, int alignment) - { - // This overload is here just for the disambiguation purposes. - AppendFormatted(value, alignment, format: null); - } - - /// - /// Appends a formatted representation of the specified value to the handler. - /// - /// The type of the value to format. - /// The value to be formatted and appended. - /// - /// The integer value that represents the total width of the formatted string. - /// Positive integers right-align the value and negative integers left-align it. - /// - /// The format string to be used. - public void AppendFormatted(T value, int alignment, string? format) - { - if (alignment == 0) - { - AppendFormatted(value, format); - } - else if (alignment < 0) - { - int start = _builder._length; - AppendFormatted(value, format); - int padding = -alignment - (_builder._length - start); - if (padding > 0) - { - _builder.Append(' ', padding); - } - } - else - { - // Since the value is right-aligned, it's necessary to format it into a temporary buffer first. - // After that, copy the formatted value into the handler, ensuring proper alignment. - - // Note: `FormatAndAppend` doesn't support custom formatting. - // We need to call `AppendCustomFormatted` explicitly. - if (_hasCustomFormatter) - { - AppendCustomFormatted(value, alignment, format); - } - else - { - FormatAndAppend(value, alignment, format); - } - } - } - - /// - /// Formats the specified value into a temporary buffer and appends it to the handler. - /// - /// - /// This method does not support custom formatters. - /// - /// - [SkipLocalsInit] // 256 * sizeof(char) == 0.5 KiB, that's a lot to init for nothing. - private void FormatAndAppend(T value, int alignment, string? format) - { - Span chars = stackalloc char[StringHelper.StackallocCharBufferSizeLimit]; - int charsWritten = 0; - - if (value is IFormattable) - { - if (typeof(T).IsEnum) - { - if (EnumHelper.TryFormatUnconstrained(value, chars, out charsWritten, format)) - { - AppendFormatted(chars.Slice(0, charsWritten), alignment); - return; - } - } - else if (value is ISpanFormattable) - { - if (((ISpanFormattable)value).TryFormat(chars, out charsWritten, format, _provider)) - { - AppendFormatted(chars.Slice(0, charsWritten), alignment); - return; - } - } - - ReadOnlySpan formattedValue = ((IFormattable)value).ToString(format, _provider).AsSpan(); - AppendFormatted(formattedValue, alignment); - } - else - { - ReadOnlySpan stringifiedValue = value?.ToString(); - AppendFormatted(stringifiedValue, alignment); - } - } - - /// - public void AppendFormatted(scoped ReadOnlySpan value) => _builder.Append(value); - - /// - public void AppendFormatted(scoped ReadOnlySpan value, int alignment = 0, string? format = null) - { - // The format is meaningless for spans and nobody should really specify it. - // If somebody does specify it, well, we just ignore it. - _ = format; - - if (alignment == 0) - { - _builder.Append(value); - return; - } - - bool leftAlign = false; - if (alignment < 0) - { - leftAlign = true; - alignment = -alignment; - } - - int padding = alignment - value.Length; - if (padding <= 0) - { - _builder.Append(value); - } - else if (leftAlign) - { - _builder.Append(value); - _builder.Append(' ', padding); - } - else - { - _builder.Append(' ', padding); - _builder.Append(value); - } - } - - /// - public void AppendFormatted(string? value) - { - // If there's a custom formatter, let it deal with the formatting. - if (_hasCustomFormatter) - { - AppendCustomFormatted(value, alignment: 0, format: null); - return; - } - - _builder.Append(value); - } - - /// - public void AppendFormatted(string? value, int alignment = 0, string? format = null) - { - // The format is meaningless for strings and nobody should really specify it. This overload exists mainly - // to disambiguate between ReadOnlySpan and object overloads, especially in cases where someone - // does specify a format, as a string is implicitly convertible to both. - // We'll just delegate to the T-based implementation to deal with it. - AppendFormatted(value, alignment, format); - } - - /// - public void AppendFormatted(object? value, int alignment = 0, string? format = null) - { - // This overload is expected to be used very rarely, and only in the following scenarios: - // a) When something strongly typed as an object is formatted with both an alignment and a format. - // b) When the compiler is unable to determine the target type for T. - // This overload is here primarily to ensure that cases from (b) can compile. - // We'll just delegate to the T-based implementation to deal with it. - AppendFormatted(value, alignment, format); - } - - /// - /// Formats the specified value using a custom formatter and appends it to the handler. - /// - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void AppendCustomFormatted(T value, int alignment, string? format) - { - Debug.Assert(_hasCustomFormatter); - Debug.Assert(_provider is not null); - - ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter)); - Debug.Assert(formatter is not null, "Where did the custom formatter go?"); - - string? formattedValue = formatter?.Format(format, value, _provider); - AppendFormatted(formattedValue.AsSpan(), alignment); - } - } -} - -#pragma warning restore IDE0060 // The "unused" parameters are required by the compiler. diff --git a/src/Spanned/Text/ValueStringBuilder.AppendJoin.cs b/src/Spanned/Text/ValueStringBuilder.AppendJoin.cs index b3dd09e..a1a9455 100644 --- a/src/Spanned/Text/ValueStringBuilder.AppendJoin.cs +++ b/src/Spanned/Text/ValueStringBuilder.AppendJoin.cs @@ -19,7 +19,7 @@ public ref partial struct ValueStringBuilder public void AppendJoin(string? separator, scoped ReadOnlySpan values) { separator ??= string.Empty; - AppendJoinCore(in separator.GetPinnableReference(), separator.Length, values); + AppendJoinCore(ref MemoryMarshal.GetReference(separator.AsSpan()), separator.Length, values); } /// @@ -38,7 +38,7 @@ public void AppendJoin(string? separator, scoped ReadOnlySpan values) /// public void AppendJoin(char separator, scoped ReadOnlySpan values) { - AppendJoinCore(in separator, 1, values); + AppendJoinCore(ref separator, 1, values); } /// @@ -58,16 +58,16 @@ public void AppendJoin(char separator, scoped ReadOnlySpan values) /// is a null. public void AppendJoin(string? separator, IEnumerable values) { - ArgumentNullException.ThrowIfNull(values); + ThrowHelper.ThrowArgumentNullException_IfNull(values); separator ??= string.Empty; if (values.TryGetSpan(out ReadOnlySpan span)) { - AppendJoinCore(in separator.GetPinnableReference(), separator.Length, span); + AppendJoinCore(ref MemoryMarshal.GetReference(separator.AsSpan()), separator.Length, span); } else { - AppendJoinCore(in separator.GetPinnableReference(), separator.Length, values); + AppendJoinCore(ref MemoryMarshal.GetReference(separator.AsSpan()), separator.Length, values); } } @@ -92,11 +92,11 @@ public void AppendJoin(char separator, IEnumerable values) if (values.TryGetSpan(out ReadOnlySpan span)) { - AppendJoinCore(in separator, 1, span); + AppendJoinCore(ref separator, 1, span); } else { - AppendJoinCore(in separator, 1, values); + AppendJoinCore(ref separator, 1, values); } } @@ -120,7 +120,7 @@ public void AppendJoin(string? separator, params string?[] values) ThrowHelper.ThrowArgumentNullException_IfNull(values); separator ??= string.Empty; - AppendJoinCore(in separator.GetPinnableReference(), separator.Length, (ReadOnlySpan)values); + AppendJoinCore(ref MemoryMarshal.GetReference(separator.AsSpan()), separator.Length, (ReadOnlySpan)values); } /// @@ -142,7 +142,7 @@ public void AppendJoin(char separator, params string?[] values) { ThrowHelper.ThrowArgumentNullException_IfNull(values); - AppendJoinCore(in separator, 1, (ReadOnlySpan)values); + AppendJoinCore(ref separator, 1, (ReadOnlySpan)values); } /// @@ -165,7 +165,7 @@ public void AppendJoin(string? separator, params object?[] values) ThrowHelper.ThrowArgumentNullException_IfNull(values); separator ??= string.Empty; - AppendJoinCore(in separator.GetPinnableReference(), separator.Length, (ReadOnlySpan)values); + AppendJoinCore(ref MemoryMarshal.GetReference(separator.AsSpan()), separator.Length, (ReadOnlySpan)values); } /// @@ -187,7 +187,7 @@ public void AppendJoin(char separator, params object?[] values) { ThrowHelper.ThrowArgumentNullException_IfNull(values); - AppendJoinCore(in separator, 1, (ReadOnlySpan)values); + AppendJoinCore(ref separator, 1, (ReadOnlySpan)values); } /// @@ -201,9 +201,9 @@ public void AppendJoin(char separator, params object?[] values) /// A collection that contains the objects to concatenate and append to the current /// instance of the string builder. /// - private void AppendJoinCore(in char separator, int separatorLength, IEnumerable values) + private void AppendJoinCore(ref char separator, int separatorLength, IEnumerable values) { - Debug.Assert(!Unsafe.IsNullRef(ref Unsafe.AsRef(in separator))); + Debug.Assert(!Unsafe.IsNullRef(ref separator)); Debug.Assert(separatorLength >= 0); Debug.Assert(values is not null); @@ -242,9 +242,9 @@ private void AppendJoinCore(in char separator, int separatorLength, IEnumerab /// A span that contains the objects to concatenate and append to the current /// instance of the string builder. /// - private void AppendJoinCore(in char separator, int separatorLength, scoped ReadOnlySpan values) + private void AppendJoinCore(ref char separator, int separatorLength, scoped ReadOnlySpan values) { - Debug.Assert(!Unsafe.IsNullRef(ref Unsafe.AsRef(in separator))); + Debug.Assert(!Unsafe.IsNullRef(ref separator)); Debug.Assert(separatorLength >= 0); if (values.Length == 0) diff --git a/src/Spanned/Text/ValueStringBuilder.Insert.cs b/src/Spanned/Text/ValueStringBuilder.Insert.cs index c09fda8..9302528 100644 --- a/src/Spanned/Text/ValueStringBuilder.Insert.cs +++ b/src/Spanned/Text/ValueStringBuilder.Insert.cs @@ -146,9 +146,10 @@ public void Insert(int index, string? value, int repeatCount) _buffer.Slice(index, remaining).CopyTo(_buffer.Slice(index + count)); } + ReadOnlySpan valueSpan = value.AsSpan(); for (int i = 0; i < repeatCount; i++) { - value.CopyTo(_buffer.Slice(index + i * value.Length)); + valueSpan.CopyTo(_buffer.Slice(index + i * value.Length)); } _length += count; @@ -305,7 +306,12 @@ public void Insert(int index, ReadOnlyMemory value) /// The value to insert. /// public void Insert(int index, object? value) - => Insert(index, value); + { + if (value is not null) + { + Insert(index, value.ToString()); + } + } /// /// Inserts the string representation of a Boolean value into this instance at the @@ -610,26 +616,6 @@ public void Insert(int index, decimal value) } } - /// - /// Inserts the string representation of a specified object into this instance - /// at the specified character position. - /// - /// The type of the value to insert. - /// The position in this instance where insertion begins. - /// The value to insert. - /// - public void Insert(int index, T? value) - { - if (typeof(T).IsEnum || value is ISpanFormattable) - { - FormatAndInsert(index, value, format: null, provider: null); - } - else if (value is not null) - { - Insert(index, value.ToString()); - } - } - /// /// Inserts a formatted representation of the specified value into this instance /// at the specified character position. @@ -649,25 +635,9 @@ public void Insert(int index, T? value, string? format = null, IFormatProvide return; } - // First, check for `IFormattable`, even though `ISpanFormattable` is preferred. The latter - // requires the former anyway. - // - // For value types, it won't matter since the type checks become JIT-time constants. - // - // For reference types, it's more probable they implement `IFormattable` over `ISpanFormattable`. - // So, if they don't implement either, we save an interface check over first checking for `ISpanFormattable` - // and then for `IFormattable`, and if they only implement `IFormattable`, we come out even. - // Only if they implement both we end up paying for an extra interface check. if (value is IFormattable) { - if (typeof(T).IsEnum || value is ISpanFormattable) - { - FormatAndInsert(index, value, format, provider); - } - else - { - Insert(index, ((IFormattable)value).ToString(format, provider)); - } + Insert(index, ((IFormattable)value).ToString(format, provider)); } else if (value is not null) { @@ -687,31 +657,10 @@ public void Insert(int index, T? value, string? format = null, IFormatProvide /// /// This method does not support custom formatters. /// - [SkipLocalsInit] // 256 * sizeof(char) == 0.5 KiB, that's a lot to init for nothing. private void FormatAndInsert(int index, T? value, string? format, IFormatProvider? provider) { - Span chars = stackalloc char[StringHelper.StackallocCharBufferSizeLimit]; - int charsWritten = 0; - if (value is IFormattable) { - if (typeof(T).IsEnum) - { - if (EnumHelper.TryFormatUnconstrained(value, chars, out charsWritten, format)) - { - Insert(index, chars.Slice(0, charsWritten)); - return; - } - } - else if (value is ISpanFormattable) - { - if (((ISpanFormattable)value).TryFormat(chars, out charsWritten, format, provider)) - { - Insert(index, chars.Slice(0, charsWritten)); - return; - } - } - Insert(index, ((IFormattable)value).ToString(format, provider)); } else if (value is not null) diff --git a/src/Spanned/Text/ValueStringBuilder.cs b/src/Spanned/Text/ValueStringBuilder.cs index 6478479..4b71439 100644 --- a/src/Spanned/Text/ValueStringBuilder.cs +++ b/src/Spanned/Text/ValueStringBuilder.cs @@ -379,7 +379,7 @@ public void Remove(int start, int length) /// The character to replace. /// The character that replaces oldChar. public readonly void Replace(char oldChar, char newChar) - => AsSpan().Replace(oldChar, newChar); + => Replace(oldChar, newChar, 0, _length); /// /// Replaces, within a substring of this instance, all occurrences of a specified @@ -391,7 +391,17 @@ public readonly void Replace(char oldChar, char newChar) /// The length of the substring. /// public readonly void Replace(char oldChar, char newChar, int start, int length) - => AsSpan(start, length).Replace(oldChar, newChar); + { + Span chars = AsSpan(start, length); + + int i = chars.IndexOf(oldChar); + while (i >= 0) + { + chars[i] = newChar; + chars = chars.Slice(i + 1); + i = chars.IndexOf(oldChar); + } + } /// /// Replaces all occurrences of a specified string in this instance with another @@ -451,13 +461,6 @@ public void Replace(scoped ReadOnlySpan oldValue, scoped ReadOnlySpan