26.06.02
This commit is contained in:
955
QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs
Normal file
955
QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs
Normal file
@@ -0,0 +1,955 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace QWERTYkez.Mensura.Extensions;
|
||||
|
||||
public static partial class AggregateUnitExtensions
|
||||
{
|
||||
// Sum Average Max Min (не nullable) ==========================================
|
||||
|
||||
|
||||
// === ReadOnlySpan === SIMD
|
||||
public static T Sum<T>(this ReadOnlySpan<T> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty) return default;
|
||||
|
||||
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
||||
double sum = 0;
|
||||
int i = 0;
|
||||
|
||||
// 1. ПУТЬ AVX2 (Intel/AMD) — Самый быстрый, обрабатывает по 4 элемента через Unsafe
|
||||
if (Avx2.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vSum = Vector256<double>.Zero;
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vSum = Avx2.Add(vSum, v);
|
||||
}
|
||||
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
||||
}
|
||||
// 2. ПУТЬ AVX (Старые x64 CPU) — Чуть медленнее AVX2, но тоже по 4 элемента
|
||||
else if (Avx.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vSum = Vector256<double>.Zero;
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vSum = Avx.Add(vSum, v);
|
||||
}
|
||||
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
||||
}
|
||||
// 3. ПУТЬ VECTOR (ARM64 / Apple Silicon / SSE) — Кроссплатформенный SIMD fallback
|
||||
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
||||
{
|
||||
int vCount = Vector<double>.Count;
|
||||
Vector<double> vSum = Vector<double>.Zero;
|
||||
|
||||
for (; i <= values.Length - vCount; i += vCount)
|
||||
{
|
||||
var v = new Vector<double>(values.Slice(i, vCount));
|
||||
vSum += v;
|
||||
}
|
||||
|
||||
for (int j = 0; j < vCount; j++)
|
||||
{
|
||||
sum += vSum[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Хвост массива (или обычный расчет, если SIMD вообще нет)
|
||||
for (; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
|
||||
return Unsafe.As<double, T>(ref sum);
|
||||
}
|
||||
public static T Average<T>(this ReadOnlySpan<T> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
// Если коллекция пустая, возвращаем структуру, содержащую double.NaN
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double nan = double.NaN;
|
||||
return Unsafe.As<double, T>(ref nan);
|
||||
}
|
||||
|
||||
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
||||
double sum = 0;
|
||||
int i = 0;
|
||||
|
||||
// 1. ПУТЬ AVX2 (Intel/AMD)
|
||||
if (Avx2.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vSum = Vector256<double>.Zero;
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vSum = Avx2.Add(vSum, v);
|
||||
}
|
||||
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
||||
}
|
||||
// 2. ПУТЬ AVX (Старые x64 CPU)
|
||||
else if (Avx.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vSum = Vector256<double>.Zero;
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vSum = Avx.Add(vSum, v);
|
||||
}
|
||||
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
||||
}
|
||||
// 3. ПУТЬ VECTOR (ARM64 / SSE)
|
||||
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
||||
{
|
||||
int vCount = Vector<double>.Count;
|
||||
Vector<double> vSum = Vector<double>.Zero;
|
||||
|
||||
for (; i <= values.Length - vCount; i += vCount)
|
||||
{
|
||||
var v = new Vector<double>(values.Slice(i, vCount));
|
||||
vSum += v;
|
||||
}
|
||||
|
||||
for (int j = 0; j < vCount; j++)
|
||||
{
|
||||
sum += vSum[j];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Довычисление хвоста
|
||||
for (; i < values.Length; i++)
|
||||
{
|
||||
sum += values[i];
|
||||
}
|
||||
|
||||
// Находим среднее значение
|
||||
double average = sum / values.Length;
|
||||
|
||||
// Упаковываем double обратно в структуру T (zero-cost)
|
||||
return Unsafe.As<double, T>(ref average);
|
||||
}
|
||||
public static T Max<T>(this ReadOnlySpan<T> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double minVal = double.MinValue;
|
||||
return Unsafe.As<double, T>(ref minVal);
|
||||
}
|
||||
|
||||
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
||||
double max = double.MinValue;
|
||||
int i = 0;
|
||||
|
||||
if (Avx2.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vMax = Vector256.Create(double.MinValue);
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vMax = Avx2.Max(vMax, v);
|
||||
}
|
||||
max = Math.Max(Math.Max(vMax.GetElement(0), vMax.GetElement(1)), Math.Max(vMax.GetElement(2), vMax.GetElement(3)));
|
||||
}
|
||||
else if (Avx.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vMax = Vector256.Create(double.MinValue);
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vMax = Avx.Max(vMax, v);
|
||||
}
|
||||
max = Math.Max(Math.Max(vMax.GetElement(0), vMax.GetElement(1)), Math.Max(vMax.GetElement(2), vMax.GetElement(3)));
|
||||
}
|
||||
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
||||
{
|
||||
int vCount = Vector<double>.Count;
|
||||
var vMax = new Vector<double>(double.MinValue);
|
||||
|
||||
for (; i <= values.Length - vCount; i += vCount)
|
||||
{
|
||||
var v = new Vector<double>(values.Slice(i, vCount));
|
||||
vMax = Vector.Max(vMax, v);
|
||||
}
|
||||
|
||||
for (int j = 0; j < vCount; j++)
|
||||
{
|
||||
if (vMax[j] > max) max = vMax[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Довычисление хвоста
|
||||
for (; i < values.Length; i++)
|
||||
{
|
||||
if (values[i] > max) max = values[i];
|
||||
}
|
||||
|
||||
return Unsafe.As<double, T>(ref max);
|
||||
}
|
||||
public static T Min<T>(this ReadOnlySpan<T> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double maxVal = double.MaxValue;
|
||||
return Unsafe.As<double, T>(ref maxVal);
|
||||
}
|
||||
|
||||
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
||||
double min = double.MaxValue;
|
||||
int i = 0;
|
||||
|
||||
if (Avx2.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vMin = Vector256.Create(double.MaxValue);
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vMin = Avx2.Min(vMin, v);
|
||||
}
|
||||
min = Math.Min(Math.Min(vMin.GetElement(0), vMin.GetElement(1)), Math.Min(vMin.GetElement(2), vMin.GetElement(3)));
|
||||
}
|
||||
else if (Avx.IsSupported && values.Length >= 4)
|
||||
{
|
||||
Vector256<double> vMin = Vector256.Create(double.MaxValue);
|
||||
ref double start = ref MemoryMarshal.GetReference(values);
|
||||
|
||||
for (; i <= values.Length - 4; i += 4)
|
||||
{
|
||||
ref double currentRef = ref Unsafe.Add(ref start, i);
|
||||
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
||||
vMin = Avx.Min(vMin, v);
|
||||
}
|
||||
min = Math.Min(Math.Min(vMin.GetElement(0), vMin.GetElement(1)), Math.Min(vMin.GetElement(2), vMin.GetElement(3)));
|
||||
}
|
||||
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
||||
{
|
||||
int vCount = Vector<double>.Count;
|
||||
var vMin = new Vector<double>(double.MaxValue);
|
||||
|
||||
for (; i <= values.Length - vCount; i += vCount)
|
||||
{
|
||||
var v = new Vector<double>(values.Slice(i, vCount));
|
||||
vMin = Vector.Min(vMin, v);
|
||||
}
|
||||
|
||||
for (int j = 0; j < vCount; j++)
|
||||
{
|
||||
if (vMin[j] < min) min = vMin[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Довычисление хвоста
|
||||
for (; i < values.Length; i++)
|
||||
{
|
||||
if (values[i] < min) min = values[i];
|
||||
}
|
||||
|
||||
return Unsafe.As<double, T>(ref min);
|
||||
}
|
||||
|
||||
// === List<T> ===
|
||||
public static T Sum<T>(this List<T> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Sum();
|
||||
}
|
||||
public static T Average<T>(this List<T> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Average();
|
||||
}
|
||||
public static T Max<T>(this List<T> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Max();
|
||||
}
|
||||
public static T Min<T>(this List<T> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Min();
|
||||
}
|
||||
|
||||
// === ICollection<T> ===
|
||||
public static T Sum<T>(this ICollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return default;
|
||||
if (collection is T[] array) return array.Sum();
|
||||
if (collection is List<T> list) return list.Sum();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0); // Встроенный CopyTo работает быстрее, чем ручной foreach
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Sum();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Average<T>(this ICollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Average();
|
||||
if (collection is List<T> list) return list.Average();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Average();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Max<T>(this ICollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Max();
|
||||
if (collection is List<T> list) return list.Max();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Max();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Min<T>(this ICollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Min();
|
||||
if (collection is List<T> list) return list.Min();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Min();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
|
||||
// === IReadOnlyCollection<T> ===
|
||||
public static T Sum<T>(this IReadOnlyCollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return default;
|
||||
if (collection is T[] array) return array.Sum();
|
||||
if (collection is List<T> list) return list.Sum();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Sum();
|
||||
}
|
||||
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Average<T>(this IReadOnlyCollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Average();
|
||||
if (collection is List<T> list) return list.Average();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Average();
|
||||
}
|
||||
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Max<T>(this IReadOnlyCollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Max();
|
||||
if (collection is List<T> list) return list.Max();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Max();
|
||||
}
|
||||
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Min<T>(this IReadOnlyCollection<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
||||
if (collection is T[] array) return array.Min();
|
||||
if (collection is List<T> list) return list.Min();
|
||||
|
||||
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Min();
|
||||
}
|
||||
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
||||
}
|
||||
|
||||
|
||||
// === IEnumerable<T> ===
|
||||
public static T Sum<T>(this IEnumerable<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return default;
|
||||
|
||||
// Быстрый SIMD-путь для готовых коллекций (0 аллокаций)
|
||||
if (collection is T[] array) return array.Sum();
|
||||
if (collection is List<T> list) return list.Sum();
|
||||
if (collection is ICollection<T> col) return col.Sum();
|
||||
if (collection is IReadOnlyCollection<T> roc) return roc.Sum();
|
||||
|
||||
// Медленный путь для yield return: считаем на лету без буферов
|
||||
double sum = 0;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
sum += item.ToDouble();
|
||||
hasElements = true;
|
||||
}
|
||||
|
||||
return hasElements ? sum.ToUnit<T>() : default;
|
||||
}
|
||||
public static T Average<T>(this IEnumerable<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.NaN.ToUnit<T>();
|
||||
|
||||
if (collection is T[] array) return array.Average();
|
||||
if (collection is List<T> list) return list.Average();
|
||||
if (collection is ICollection<T> col) return col.Average();
|
||||
if (collection is IReadOnlyCollection<T> roc) return roc.Average();
|
||||
|
||||
double sum = 0;
|
||||
long count = 0;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
sum += item.ToDouble();
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0) return double.NaN.ToUnit<T>();
|
||||
|
||||
double avg = sum / count;
|
||||
return avg.ToUnit<T>();
|
||||
}
|
||||
public static T Max<T>(this IEnumerable<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.MinValue.ToUnit<T>();
|
||||
|
||||
if (collection is T[] array) return array.Max();
|
||||
if (collection is List<T> list) return list.Max();
|
||||
if (collection is ICollection<T> col) return col.Max();
|
||||
if (collection is IReadOnlyCollection<T> roc) return roc.Max();
|
||||
|
||||
double max = double.MinValue;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
double val = item.ToDouble();
|
||||
if (val > max) max = val;
|
||||
hasElements = true;
|
||||
}
|
||||
|
||||
return hasElements ? max.ToUnit<T>() : double.MinValue.ToUnit<T>();
|
||||
}
|
||||
public static T Min<T>(this IEnumerable<T> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.MaxValue.ToUnit<T>();
|
||||
|
||||
if (collection is T[] array) return array.Min();
|
||||
if (collection is List<T> list) return list.Min();
|
||||
if (collection is ICollection<T> col) return col.Min();
|
||||
if (collection is IReadOnlyCollection<T> roc) return roc.Min();
|
||||
|
||||
double min = double.MaxValue;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
double val = item.ToDouble();
|
||||
if (val < min) min = val;
|
||||
hasElements = true;
|
||||
}
|
||||
|
||||
return hasElements ? min.ToUnit<T>() : double.MaxValue.ToUnit<T>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Sum Average Max Min (nullable) ==========================================
|
||||
|
||||
|
||||
// === ReadOnlySpan ===
|
||||
public static T Sum<T>(this ReadOnlySpan<T?> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty) return default;
|
||||
|
||||
// Берем массив из пула строго под размер исходного Span
|
||||
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
||||
int validCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < units.Length; i++)
|
||||
{
|
||||
T? unit = units[i];
|
||||
if (unit.HasValue)
|
||||
{
|
||||
sharedArray[validCount++] = unit.Value.ToDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0) return default;
|
||||
|
||||
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
||||
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
||||
|
||||
return validStructs.Sum(); // Наш SIMD метод
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Average<T>(this ReadOnlySpan<T?> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double nan = double.NaN;
|
||||
return Unsafe.As<double, T>(ref nan);
|
||||
}
|
||||
|
||||
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
||||
int validCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < units.Length; i++)
|
||||
{
|
||||
T? unit = units[i];
|
||||
if (unit.HasValue)
|
||||
{
|
||||
sharedArray[validCount++] = unit.Value.ToDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
double nan = double.NaN;
|
||||
return Unsafe.As<double, T>(ref nan);
|
||||
}
|
||||
|
||||
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
||||
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
||||
|
||||
return validStructs.Average(); // Наш SIMD метод
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Max<T>(this ReadOnlySpan<T?> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double minVal = double.MinValue;
|
||||
return Unsafe.As<double, T>(ref minVal);
|
||||
}
|
||||
|
||||
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
||||
int validCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < units.Length; i++)
|
||||
{
|
||||
T? unit = units[i];
|
||||
if (unit.HasValue)
|
||||
{
|
||||
sharedArray[validCount++] = unit.Value.ToDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
double minVal = double.MinValue;
|
||||
return Unsafe.As<double, T>(ref minVal);
|
||||
}
|
||||
|
||||
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
||||
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
||||
|
||||
return validStructs.Max(); // Наш SIMD метод
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Min<T>(this ReadOnlySpan<T?> units)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (units.IsEmpty)
|
||||
{
|
||||
double maxVal = double.MaxValue;
|
||||
return Unsafe.As<double, T>(ref maxVal);
|
||||
}
|
||||
|
||||
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
||||
int validCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < units.Length; i++)
|
||||
{
|
||||
T? unit = units[i];
|
||||
if (unit.HasValue)
|
||||
{
|
||||
sharedArray[validCount++] = unit.Value.ToDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
double maxVal = double.MaxValue;
|
||||
return Unsafe.As<double, T>(ref maxVal);
|
||||
}
|
||||
|
||||
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
||||
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
||||
|
||||
return validStructs.Min(); // Наш SIMD метод
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
|
||||
// === List<T> ===
|
||||
public static T Sum<T>(this List<T?> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Sum();
|
||||
}
|
||||
public static T Average<T>(this List<T?> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Average();
|
||||
}
|
||||
public static T Max<T>(this List<T?> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Max();
|
||||
}
|
||||
public static T Min<T>(this List<T?> list)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(list).Min();
|
||||
}
|
||||
|
||||
// === ICollection<T> ===
|
||||
public static T Sum<T>(this ICollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return default;
|
||||
if (collection is T?[] array) return array.Sum();
|
||||
if (collection is List<T?> list) return list.Sum();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Sum();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T?>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Average<T>(this ICollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Average();
|
||||
if (collection is List<T?> list) return list.Average();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Average();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T?>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Max<T>(this ICollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Max();
|
||||
if (collection is List<T?> list) return list.Max();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Max();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T?>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
public static T Min<T>(this ICollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Min();
|
||||
if (collection is List<T?> list) return list.Min();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
try
|
||||
{
|
||||
collection.CopyTo(sharedArray, 0);
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Min();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<T?>.Shared.Return(sharedArray);
|
||||
}
|
||||
}
|
||||
|
||||
// === IReadOnlyCollection<T> ===
|
||||
public static T Sum<T>(this IReadOnlyCollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return default;
|
||||
if (collection is T?[] array) return array.Sum();
|
||||
if (collection is List<T?> list) return list.Sum();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Sum();
|
||||
}
|
||||
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Average<T>(this IReadOnlyCollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Average();
|
||||
if (collection is List<T?> list) return list.Average();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Average();
|
||||
}
|
||||
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Max<T>(this IReadOnlyCollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Max();
|
||||
if (collection is List<T?> list) return list.Max();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Max();
|
||||
}
|
||||
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
||||
}
|
||||
public static T Min<T>(this IReadOnlyCollection<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
||||
if (collection is T?[] array) return array.Min();
|
||||
if (collection is List<T?> list) return list.Min();
|
||||
|
||||
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
||||
int index = 0;
|
||||
try
|
||||
{
|
||||
foreach (var item in collection) sharedArray[index++] = item;
|
||||
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Min();
|
||||
}
|
||||
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
||||
}
|
||||
|
||||
// === IEnumerable<T> ===
|
||||
public static T Sum<T>(this IEnumerable<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return default;
|
||||
|
||||
// Быстрый SIMD-путь для готовых коллекций за 0 аллокаций
|
||||
if (collection is T?[] array) return array.Sum();
|
||||
if (collection is List<T?> list) return list.Sum();
|
||||
if (collection is ICollection<T?> col) return col.Sum();
|
||||
if (collection is IReadOnlyCollection<T?> roc) return roc.Sum();
|
||||
|
||||
// Медленный путь для ленивого yield return: вычисляем на лету
|
||||
double sum = 0;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (T? item in collection)
|
||||
{
|
||||
if (item.HasValue)
|
||||
{
|
||||
sum += item.Value.ToDouble();
|
||||
hasElements = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasElements ? sum.ToUnit<T>() : default;
|
||||
}
|
||||
public static T Average<T>(this IEnumerable<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.NaN.ToUnit<T>();
|
||||
|
||||
if (collection is T?[] array) return array.Average();
|
||||
if (collection is List<T?> list) return list.Average();
|
||||
if (collection is ICollection<T?> col) return col.Average();
|
||||
if (collection is IReadOnlyCollection<T?> roc) return roc.Average();
|
||||
|
||||
double sum = 0;
|
||||
long count = 0;
|
||||
|
||||
foreach (T? item in collection)
|
||||
{
|
||||
if (item.HasValue)
|
||||
{
|
||||
sum += item.Value.ToDouble();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) return double.NaN.ToUnit<T>();
|
||||
|
||||
double avg = sum / count;
|
||||
return avg.ToUnit<T>();
|
||||
}
|
||||
public static T Max<T>(this IEnumerable<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.MinValue.ToUnit<T>();
|
||||
|
||||
if (collection is T?[] array) return array.Max();
|
||||
if (collection is List<T?> list) return list.Max();
|
||||
if (collection is ICollection<T?> col) return col.Max();
|
||||
if (collection is IReadOnlyCollection<T?> roc) return roc.Max();
|
||||
|
||||
double max = double.MinValue;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (T? item in collection)
|
||||
{
|
||||
if (item.HasValue)
|
||||
{
|
||||
double val = item.Value.ToDouble();
|
||||
if (val > max) max = val;
|
||||
hasElements = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasElements ? max.ToUnit<T>() : double.MinValue.ToUnit<T>();
|
||||
}
|
||||
public static T Min<T>(this IEnumerable<T?> collection)
|
||||
where T : struct, IMensuraUnit, IEquatable<T>
|
||||
{
|
||||
if (collection == null) return double.MaxValue.ToUnit<T>();
|
||||
|
||||
if (collection is T?[] array) return array.Min();
|
||||
if (collection is List<T?> list) return list.Min();
|
||||
if (collection is ICollection<T?> col) return col.Min();
|
||||
if (collection is IReadOnlyCollection<T?> roc) return roc.Min();
|
||||
|
||||
double min = double.MaxValue;
|
||||
bool hasElements = false;
|
||||
|
||||
foreach (T? item in collection)
|
||||
{
|
||||
if (item.HasValue)
|
||||
{
|
||||
double val = item.Value.ToDouble();
|
||||
if (val < min) min = val;
|
||||
hasElements = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasElements ? min.ToUnit<T>() : double.MaxValue.ToUnit<T>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user