updates from mac

This commit is contained in:
Miha Kralj
2025-11-28 13:35:16 -08:00
parent 74b49d2bb4
commit acac3e610c
55 changed files with 126278 additions and 126081 deletions
+249 -249
View File
@@ -1,249 +1,249 @@
using System;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class SimdExtensionsTests
{
[Fact]
public void SumSIMD_EmptySpan_ReturnsZero()
{
var span = ReadOnlySpan<double>.Empty;
Assert.Equal(0.0, span.SumSIMD());
}
[Fact]
public void SumSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.SumSIMD());
}
[Fact]
public void SumSIMD_MultipleElements_ReturnsCorrectSum()
{
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(55.0, span.SumSIMD(), precision: 10);
}
[Fact]
public void SumSIMD_LargeArray_ReturnsCorrectSum()
{
double[] data = new double[1000];
for (int i = 0; i < data.Length; i++)
data[i] = i + 1.0;
var span = new ReadOnlySpan<double>(data);
double expected = 1000.0 * 1001.0 / 2.0; // Sum of 1..1000
Assert.Equal(expected, span.SumSIMD(), precision: 8);
}
[Fact]
public void MinSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.MinSIMD()));
}
[Fact]
public void MinSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.MinSIMD());
}
[Fact]
public void MinSIMD_MultipleElements_ReturnsMinimum()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(1.0, span.MinSIMD());
}
[Fact]
public void MaxSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.MaxSIMD()));
}
[Fact]
public void MaxSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.MaxSIMD());
}
[Fact]
public void MaxSIMD_MultipleElements_ReturnsMaximum()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(9.0, span.MaxSIMD());
}
[Fact]
public void AverageSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.AverageSIMD()));
}
[Fact]
public void AverageSIMD_MultipleElements_ReturnsCorrectAverage()
{
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(3.0, span.AverageSIMD(), precision: 10);
}
[Fact]
public void VarianceSIMD_LessThanTwoElements_ReturnsNaN()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.True(double.IsNaN(span.VarianceSIMD()));
}
[Fact]
public void VarianceSIMD_MultipleElements_ReturnsCorrectVariance()
{
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
var span = new ReadOnlySpan<double>(data);
// Expected variance: 4.571428... (sample variance)
double variance = span.VarianceSIMD();
Assert.True(Math.Abs(variance - 4.571428) < 0.0001);
}
[Fact]
public void StdDevSIMD_MultipleElements_ReturnsCorrectStdDev()
{
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
var span = new ReadOnlySpan<double>(data);
// Expected std dev: sqrt(4.571428) ≈ 2.138
double stdDev = span.StdDevSIMD();
Assert.True(Math.Abs(stdDev - 2.138) < 0.01);
}
[Fact]
public void MinMaxSIMD_EmptySpan_ReturnsBothNaN()
{
var span = ReadOnlySpan<double>.Empty;
var (min, max) = span.MinMaxSIMD();
Assert.True(double.IsNaN(min));
Assert.True(double.IsNaN(max));
}
[Fact]
public void MinMaxSIMD_SingleElement_ReturnsSameValue()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
var (min, max) = span.MinMaxSIMD();
Assert.Equal(42.5, min);
Assert.Equal(42.5, max);
}
[Fact]
public void MinMaxSIMD_MultipleElements_ReturnsCorrectMinMax()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
var (min, max) = span.MinMaxSIMD();
Assert.Equal(1.0, min);
Assert.Equal(9.0, max);
}
[Fact]
public void SIMD_WorksWithTSeriesValues()
{
var series = new TSeries(100);
for (int i = 0; i < 100; i++)
{
series.Add(DateTime.UtcNow.Ticks + i, i + 1.0);
}
var values = series.Values;
double sum = values.SumSIMD();
double avg = values.AverageSIMD();
double min = values.MinSIMD();
double max = values.MaxSIMD();
var (minAlt, maxAlt) = values.MinMaxSIMD();
Assert.Equal(5050.0, sum, precision: 8); // Sum of 1..100
Assert.Equal(50.5, avg, precision: 8);
Assert.Equal(1.0, min);
Assert.Equal(100.0, max);
Assert.Equal(min, minAlt);
Assert.Equal(max, maxAlt);
}
[Fact]
public void SIMD_WorksWithTBarSeriesClose()
{
var gbm = new GBM(startPrice: 100.0);
long startTime = DateTime.UtcNow.Ticks;
var interval = TimeSpan.FromMinutes(1);
var bars = gbm.Fetch(1000, startTime, interval);
var closeValues = bars.Close.Values;
double sum = closeValues.SumSIMD();
double avg = closeValues.AverageSIMD();
double min = closeValues.MinSIMD();
double max = closeValues.MaxSIMD();
Assert.True(sum > 0);
Assert.True(avg > 0);
Assert.True(min > 0);
Assert.True(max > min);
}
[Fact]
public void SIMD_PerformanceTest_LargeDataset()
{
// Generate large dataset
var gbm = new GBM(startPrice: 100.0);
long startTime = DateTime.UtcNow.Ticks;
var interval = TimeSpan.FromMinutes(1);
var bars = gbm.Fetch(10000, startTime, interval);
var closeValues = bars.Close.Values;
// Warm up
_ = closeValues.SumSIMD();
// Test SIMD operations
var sw = System.Diagnostics.Stopwatch.StartNew();
double sum = closeValues.SumSIMD();
double avg = closeValues.AverageSIMD();
double min = closeValues.MinSIMD();
double max = closeValues.MaxSIMD();
var (minAlt, maxAlt) = closeValues.MinMaxSIMD();
double variance = closeValues.VarianceSIMD();
double stdDev = closeValues.StdDevSIMD();
sw.Stop();
// Verify results are valid
Assert.True(sum > 0);
Assert.True(avg > 0);
Assert.True(min > 0);
Assert.True(max > min);
Assert.True(variance > 0);
Assert.True(stdDev > 0);
// Performance should be sub-millisecond for 10k elements
Assert.True(sw.ElapsedMilliseconds < 10,
$"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 10ms");
}
}
using System;
using Xunit;
using QuanTAlib;
namespace QuanTAlib.Tests;
public class SimdExtensionsTests
{
[Fact]
public void SumSIMD_EmptySpan_ReturnsZero()
{
var span = ReadOnlySpan<double>.Empty;
Assert.Equal(0.0, span.SumSIMD());
}
[Fact]
public void SumSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.SumSIMD());
}
[Fact]
public void SumSIMD_MultipleElements_ReturnsCorrectSum()
{
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(55.0, span.SumSIMD(), precision: 10);
}
[Fact]
public void SumSIMD_LargeArray_ReturnsCorrectSum()
{
double[] data = new double[1000];
for (int i = 0; i < data.Length; i++)
data[i] = i + 1.0;
var span = new ReadOnlySpan<double>(data);
double expected = 1000.0 * 1001.0 / 2.0; // Sum of 1..1000
Assert.Equal(expected, span.SumSIMD(), precision: 8);
}
[Fact]
public void MinSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.MinSIMD()));
}
[Fact]
public void MinSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.MinSIMD());
}
[Fact]
public void MinSIMD_MultipleElements_ReturnsMinimum()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(1.0, span.MinSIMD());
}
[Fact]
public void MaxSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.MaxSIMD()));
}
[Fact]
public void MaxSIMD_SingleElement_ReturnsElement()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(42.5, span.MaxSIMD());
}
[Fact]
public void MaxSIMD_MultipleElements_ReturnsMaximum()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(9.0, span.MaxSIMD());
}
[Fact]
public void AverageSIMD_EmptySpan_ReturnsNaN()
{
var span = ReadOnlySpan<double>.Empty;
Assert.True(double.IsNaN(span.AverageSIMD()));
}
[Fact]
public void AverageSIMD_MultipleElements_ReturnsCorrectAverage()
{
double[] data = [1.0, 2.0, 3.0, 4.0, 5.0];
var span = new ReadOnlySpan<double>(data);
Assert.Equal(3.0, span.AverageSIMD(), precision: 10);
}
[Fact]
public void VarianceSIMD_LessThanTwoElements_ReturnsNaN()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
Assert.True(double.IsNaN(span.VarianceSIMD()));
}
[Fact]
public void VarianceSIMD_MultipleElements_ReturnsCorrectVariance()
{
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
var span = new ReadOnlySpan<double>(data);
// Expected variance: 4.571428... (sample variance)
double variance = span.VarianceSIMD();
Assert.True(Math.Abs(variance - 4.571428) < 0.0001);
}
[Fact]
public void StdDevSIMD_MultipleElements_ReturnsCorrectStdDev()
{
double[] data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
var span = new ReadOnlySpan<double>(data);
// Expected std dev: sqrt(4.571428) ≈ 2.138
double stdDev = span.StdDevSIMD();
Assert.True(Math.Abs(stdDev - 2.138) < 0.01);
}
[Fact]
public void MinMaxSIMD_EmptySpan_ReturnsBothNaN()
{
var span = ReadOnlySpan<double>.Empty;
var (min, max) = span.MinMaxSIMD();
Assert.True(double.IsNaN(min));
Assert.True(double.IsNaN(max));
}
[Fact]
public void MinMaxSIMD_SingleElement_ReturnsSameValue()
{
double[] data = [42.5];
var span = new ReadOnlySpan<double>(data);
var (min, max) = span.MinMaxSIMD();
Assert.Equal(42.5, min);
Assert.Equal(42.5, max);
}
[Fact]
public void MinMaxSIMD_MultipleElements_ReturnsCorrectMinMax()
{
double[] data = [5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0, 4.0];
var span = new ReadOnlySpan<double>(data);
var (min, max) = span.MinMaxSIMD();
Assert.Equal(1.0, min);
Assert.Equal(9.0, max);
}
[Fact]
public void SIMD_WorksWithTSeriesValues()
{
var series = new TSeries(100);
for (int i = 0; i < 100; i++)
{
series.Add(DateTime.UtcNow.Ticks + i, i + 1.0);
}
var values = series.Values;
double sum = values.SumSIMD();
double avg = values.AverageSIMD();
double min = values.MinSIMD();
double max = values.MaxSIMD();
var (minAlt, maxAlt) = values.MinMaxSIMD();
Assert.Equal(5050.0, sum, precision: 8); // Sum of 1..100
Assert.Equal(50.5, avg, precision: 8);
Assert.Equal(1.0, min);
Assert.Equal(100.0, max);
Assert.Equal(min, minAlt);
Assert.Equal(max, maxAlt);
}
[Fact]
public void SIMD_WorksWithTBarSeriesClose()
{
var gbm = new GBM(startPrice: 100.0);
long startTime = DateTime.UtcNow.Ticks;
var interval = TimeSpan.FromMinutes(1);
var bars = gbm.Fetch(1000, startTime, interval);
var closeValues = bars.Close.Values;
double sum = closeValues.SumSIMD();
double avg = closeValues.AverageSIMD();
double min = closeValues.MinSIMD();
double max = closeValues.MaxSIMD();
Assert.True(sum > 0);
Assert.True(avg > 0);
Assert.True(min > 0);
Assert.True(max > min);
}
[Fact]
public void SIMD_PerformanceTest_LargeDataset()
{
// Generate large dataset
var gbm = new GBM(startPrice: 100.0);
long startTime = DateTime.UtcNow.Ticks;
var interval = TimeSpan.FromMinutes(1);
var bars = gbm.Fetch(10000, startTime, interval);
var closeValues = bars.Close.Values;
// Warm up
_ = closeValues.SumSIMD();
// Test SIMD operations
var sw = System.Diagnostics.Stopwatch.StartNew();
double sum = closeValues.SumSIMD();
double avg = closeValues.AverageSIMD();
double min = closeValues.MinSIMD();
double max = closeValues.MaxSIMD();
var (minAlt, maxAlt) = closeValues.MinMaxSIMD();
double variance = closeValues.VarianceSIMD();
double stdDev = closeValues.StdDevSIMD();
sw.Stop();
// Verify results are valid
Assert.True(sum > 0);
Assert.True(avg > 0);
Assert.True(min > 0);
Assert.True(max > min);
Assert.True(variance > 0);
Assert.True(stdDev > 0);
// Performance should be sub-millisecond for 10k elements
Assert.True(sw.ElapsedMilliseconds < 10,
$"SIMD operations took {sw.ElapsedMilliseconds}ms, expected < 10ms");
}
}
+280 -280
View File
@@ -1,280 +1,280 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SIMD-accelerated extension methods for high-performance array operations.
/// Uses Vector<T> for 4-8x speedup on supported hardware with automatic scalar fallback.
/// </summary>
public static class SimdExtensions
{
/// <summary>
/// Calculates sum using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double SumSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return 0.0;
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
Vector<double> sum = Vector<double>.Zero;
int vectorSize = Vector<double>.Count;
int i = 0;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
sum += vector;
}
// Horizontal sum of vector
double result = 0.0;
for (int j = 0; j < vectorSize; j++)
result += sum[j];
// Process remaining elements
for (; i < span.Length; i++)
result += span[i];
return result;
}
// Scalar fallback
double scalar = 0.0;
for (int i = 0; i < span.Length; i++)
scalar += span[i];
return scalar;
}
/// <summary>
/// Calculates minimum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MinSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
if (span.Length == 1) return span[0];
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var minVec = new Vector<double>(span.Slice(0, vectorSize));
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
minVec = Vector.Min(minVec, vector);
}
// Find minimum within vector
double result = minVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (minVec[j] < result)
result = minVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] < result)
result = span[i];
}
return result;
}
// Scalar fallback
double min = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] < min)
min = span[i];
}
return min;
}
/// <summary>
/// Calculates maximum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MaxSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
if (span.Length == 1) return span[0];
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var maxVec = new Vector<double>(span.Slice(0, vectorSize));
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
maxVec = Vector.Max(maxVec, vector);
}
// Find maximum within vector
double result = maxVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (maxVec[j] > result)
result = maxVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] > result)
result = span[i];
}
return result;
}
// Scalar fallback
double max = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] > max)
max = span[i];
}
return max;
}
/// <summary>
/// Calculates average using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double AverageSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
return span.SumSIMD() / span.Length;
}
/// <summary>
/// Calculates variance using SIMD vectorization (Welford's online algorithm adapted).
/// More numerically stable than naive two-pass algorithm.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double VarianceSIMD(this ReadOnlySpan<double> span, double? mean = null)
{
if (span.Length < 2) return double.NaN;
double m = mean ?? span.AverageSIMD();
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
var meanVec = new Vector<double>(m);
Vector<double> sumSq = Vector<double>.Zero;
int vectorSize = Vector<double>.Count;
int i = 0;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
var diff = vector - meanVec;
sumSq += diff * diff;
}
// Horizontal sum of vector
double result = 0.0;
for (int j = 0; j < vectorSize; j++)
result += sumSq[j];
// Process remaining elements
for (; i < span.Length; i++)
{
double diff = span[i] - m;
result += diff * diff;
}
return result / (span.Length - 1);
}
// Scalar fallback
double sumSquares = 0.0;
for (int i = 0; i < span.Length; i++)
{
double diff = span[i] - m;
sumSquares += diff * diff;
}
return sumSquares / (span.Length - 1);
}
/// <summary>
/// Calculates standard deviation using SIMD vectorization.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double StdDevSIMD(this ReadOnlySpan<double> span, double? mean = null)
{
return Math.Sqrt(span.VarianceSIMD(mean));
}
/// <summary>
/// Finds both min and max in a single pass using SIMD vectorization.
/// More efficient than calling MinSIMD and MaxSIMD separately.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static (double Min, double Max) MinMaxSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return (double.NaN, double.NaN);
if (span.Length == 1) return (span[0], span[0]);
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var minVec = new Vector<double>(span.Slice(0, vectorSize));
var maxVec = minVec;
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
minVec = Vector.Min(minVec, vector);
maxVec = Vector.Max(maxVec, vector);
}
// Find min/max within vectors
double min = minVec[0];
double max = maxVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (minVec[j] < min) min = minVec[j];
if (maxVec[j] > max) max = maxVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] < min) min = span[i];
if (span[i] > max) max = span[i];
}
return (min, max);
}
// Scalar fallback
double scalarMin = span[0];
double scalarMax = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] < scalarMin) scalarMin = span[i];
if (span[i] > scalarMax) scalarMax = span[i];
}
return (scalarMin, scalarMax);
}
}
using System.Numerics;
using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// SIMD-accelerated extension methods for high-performance array operations.
/// Uses Vector<T> for 4-8x speedup on supported hardware with automatic scalar fallback.
/// </summary>
public static class SimdExtensions
{
/// <summary>
/// Calculates sum using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double SumSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return 0.0;
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
Vector<double> sum = Vector<double>.Zero;
int vectorSize = Vector<double>.Count;
int i = 0;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
sum += vector;
}
// Horizontal sum of vector
double result = 0.0;
for (int j = 0; j < vectorSize; j++)
result += sum[j];
// Process remaining elements
for (; i < span.Length; i++)
result += span[i];
return result;
}
// Scalar fallback
double scalar = 0.0;
for (int i = 0; i < span.Length; i++)
scalar += span[i];
return scalar;
}
/// <summary>
/// Calculates minimum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MinSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
if (span.Length == 1) return span[0];
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var minVec = new Vector<double>(span.Slice(0, vectorSize));
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
minVec = Vector.Min(minVec, vector);
}
// Find minimum within vector
double result = minVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (minVec[j] < result)
result = minVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] < result)
result = span[i];
}
return result;
}
// Scalar fallback
double min = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] < min)
min = span[i];
}
return min;
}
/// <summary>
/// Calculates maximum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MaxSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
if (span.Length == 1) return span[0];
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var maxVec = new Vector<double>(span.Slice(0, vectorSize));
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
maxVec = Vector.Max(maxVec, vector);
}
// Find maximum within vector
double result = maxVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (maxVec[j] > result)
result = maxVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] > result)
result = span[i];
}
return result;
}
// Scalar fallback
double max = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] > max)
max = span[i];
}
return max;
}
/// <summary>
/// Calculates average using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double AverageSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return double.NaN;
return span.SumSIMD() / span.Length;
}
/// <summary>
/// Calculates variance using SIMD vectorization (Welford's online algorithm adapted).
/// More numerically stable than naive two-pass algorithm.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double VarianceSIMD(this ReadOnlySpan<double> span, double? mean = null)
{
if (span.Length < 2) return double.NaN;
double m = mean ?? span.AverageSIMD();
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
var meanVec = new Vector<double>(m);
Vector<double> sumSq = Vector<double>.Zero;
int vectorSize = Vector<double>.Count;
int i = 0;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
var diff = vector - meanVec;
sumSq += diff * diff;
}
// Horizontal sum of vector
double result = 0.0;
for (int j = 0; j < vectorSize; j++)
result += sumSq[j];
// Process remaining elements
for (; i < span.Length; i++)
{
double diff = span[i] - m;
result += diff * diff;
}
return result / (span.Length - 1);
}
// Scalar fallback
double sumSquares = 0.0;
for (int i = 0; i < span.Length; i++)
{
double diff = span[i] - m;
sumSquares += diff * diff;
}
return sumSquares / (span.Length - 1);
}
/// <summary>
/// Calculates standard deviation using SIMD vectorization.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double StdDevSIMD(this ReadOnlySpan<double> span, double? mean = null)
{
return Math.Sqrt(span.VarianceSIMD(mean));
}
/// <summary>
/// Finds both min and max in a single pass using SIMD vectorization.
/// More efficient than calling MinSIMD and MaxSIMD separately.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static (double Min, double Max) MinMaxSIMD(this ReadOnlySpan<double> span)
{
if (span.IsEmpty) return (double.NaN, double.NaN);
if (span.Length == 1) return (span[0], span[0]);
if (Vector.IsHardwareAccelerated && span.Length >= Vector<double>.Count)
{
int vectorSize = Vector<double>.Count;
var minVec = new Vector<double>(span.Slice(0, vectorSize));
var maxVec = minVec;
int i = vectorSize;
// Process in vector chunks
for (; i <= span.Length - vectorSize; i += vectorSize)
{
var vector = new Vector<double>(span.Slice(i, vectorSize));
minVec = Vector.Min(minVec, vector);
maxVec = Vector.Max(maxVec, vector);
}
// Find min/max within vectors
double min = minVec[0];
double max = maxVec[0];
for (int j = 1; j < vectorSize; j++)
{
if (minVec[j] < min) min = minVec[j];
if (maxVec[j] > max) max = maxVec[j];
}
// Process remaining elements
for (; i < span.Length; i++)
{
if (span[i] < min) min = span[i];
if (span[i] > max) max = span[i];
}
return (min, max);
}
// Scalar fallback
double scalarMin = span[0];
double scalarMax = span[0];
for (int i = 1; i < span.Length; i++)
{
if (span[i] < scalarMin) scalarMin = span[i];
if (span[i] > scalarMax) scalarMax = span[i];
}
return (scalarMin, scalarMax);
}
}
+43 -43
View File
@@ -1,43 +1,43 @@
# SimdExtensions Class
`SimdExtensions` provides high-performance, SIMD-accelerated extension methods for `ReadOnlySpan<double>`. It leverages .NET's `Vector<T>` to achieve 4-8x speedups on supported hardware (AVX2, AVX-512) while automatically falling back to scalar implementations on older hardware.
## Key Features
- **Hardware Acceleration**: Uses CPU vector registers to process multiple elements in parallel.
- **Automatic Fallback**: Gracefully handles non-SIMD hardware or small arrays.
- **Zero-Allocation**: Operates directly on spans without creating new arrays.
- **Aggressive Inlining**: Methods are marked for inlining to minimize call overhead.
## Available Methods
| Method | Description |
|--------|-------------|
| `SumSIMD()` | Calculates the sum of elements. |
| `MinSIMD()` | Finds the minimum value. |
| `MaxSIMD()` | Finds the maximum value. |
| `MinMaxSIMD()` | Finds both min and max in a single pass (more efficient than separate calls). |
| `AverageSIMD()` | Calculates the arithmetic mean. |
| `VarianceSIMD()` | Calculates the sample variance. |
| `StdDevSIMD()` | Calculates the sample standard deviation. |
## Performance
On modern CPUs (e.g., Intel Core i7/i9, AMD Ryzen), these methods typically outperform standard LINQ or scalar loops by a factor of 4 to 8 for large arrays.
## Usage
```csharp
using QuanTAlib;
double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, ... };
ReadOnlySpan<double> span = data;
// Calculate sum
double sum = span.SumSIMD();
// Calculate min and max in one pass
var (min, max) = span.MinMaxSIMD();
// Calculate standard deviation
double stdDev = span.StdDevSIMD();
# SimdExtensions Class
`SimdExtensions` provides high-performance, SIMD-accelerated extension methods for `ReadOnlySpan<double>`. It leverages .NET's `Vector<T>` to achieve 4-8x speedups on supported hardware (AVX2, AVX-512) while automatically falling back to scalar implementations on older hardware.
## Key Features
- **Hardware Acceleration**: Uses CPU vector registers to process multiple elements in parallel.
- **Automatic Fallback**: Gracefully handles non-SIMD hardware or small arrays.
- **Zero-Allocation**: Operates directly on spans without creating new arrays.
- **Aggressive Inlining**: Methods are marked for inlining to minimize call overhead.
## Available Methods
| Method | Description |
|--------|-------------|
| `SumSIMD()` | Calculates the sum of elements. |
| `MinSIMD()` | Finds the minimum value. |
| `MaxSIMD()` | Finds the maximum value. |
| `MinMaxSIMD()` | Finds both min and max in a single pass (more efficient than separate calls). |
| `AverageSIMD()` | Calculates the arithmetic mean. |
| `VarianceSIMD()` | Calculates the sample variance. |
| `StdDevSIMD()` | Calculates the sample standard deviation. |
## Performance
On modern CPUs (e.g., Intel Core i7/i9, AMD Ryzen), these methods typically outperform standard LINQ or scalar loops by a factor of 4 to 8 for large arrays.
## Usage
```csharp
using QuanTAlib;
double[] data = { 1.0, 2.0, 3.0, 4.0, 5.0, ... };
ReadOnlySpan<double> span = data;
// Calculate sum
double sum = span.SumSIMD();
// Calculate min and max in one pass
var (min, max) = span.MinMaxSIMD();
// Calculate standard deviation
double stdDev = span.StdDevSIMD();