mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-14 08:38:04 +00:00
updates from mac
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user