Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMD for CyclicBuffer aggregation #58

Merged
merged 2 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/DotRecast.Core/Buffers/RcCyclicBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,15 @@ public void Clear()
public T[] ToArray()
{
T[] newArray = new T[Size];
CopyTo(newArray);
return newArray;
}

public void CopyTo(Span<T> destination)
{
var span1 = ArrayOne();
span1.CopyTo(newArray.AsSpan());
ArrayTwo().CopyTo(newArray.AsSpan(span1.Length..));

return newArray;
span1.CopyTo(destination);
ArrayTwo().CopyTo(destination[span1.Length..]);
}

private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
Expand Down Expand Up @@ -232,7 +235,7 @@ private int InternalIndex(int index)
: index - Capacity);
}

private Span<T> ArrayOne()
internal Span<T> ArrayOne()
{
if (IsEmpty)
{
Expand All @@ -247,7 +250,7 @@ private Span<T> ArrayOne()
return new Span<T>(_buffer, _start, _buffer.Length - _start);
}

private Span<T> ArrayTwo()
internal Span<T> ArrayTwo()
{
if (IsEmpty)
{
Expand Down
121 changes: 91 additions & 30 deletions src/DotRecast.Core/Buffers/RcCyclicBuffers.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,119 @@
namespace DotRecast.Core.Buffers
using System;
using System.Numerics;
using System.Runtime.InteropServices;

namespace DotRecast.Core.Buffers
{
public static class RcCyclicBuffers
{
public static long Sum(this RcCyclicBuffer<long> source)
public static long Sum(this ReadOnlySpan<long> source)
{
long sum = 0;
checked
var buffer = source;
var result = 0L;
if (Vector.IsHardwareAccelerated)
{
// NOTE: SIMD would be nice here
foreach (var x in source)
{
sum += x;
}
}
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecSum = Vector<long>.Zero;
foreach (var vec in vectors)
vecSum += vec;

return sum;
result = Vector.Dot(vecSum, Vector<long>.One);
var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}

foreach (var val in buffer)
result += val;

return result;
}

public static double Average(this RcCyclicBuffer<long> source)
public static double Average(this ReadOnlySpan<long> source)
{
if (0 >= source.Size)
if (0 >= source.Length)
return 0;

return source.Sum() / (double)source.Size;
return source.Sum() / (double)source.Length;
}

public static long Min(this RcCyclicBuffer<long> source)
private static long Min(this ReadOnlySpan<long> source)
{
if (0 >= source.Size)
return 0;
var buffer = source;
var result = long.MaxValue;

long minValue = long.MaxValue;
foreach (var x in source)
if (Vector.IsHardwareAccelerated)
{
if (x < minValue)
minValue = x;
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecMin = Vector<long>.One * result;

foreach (var vec in vectors)
vecMin = Vector.Min(vecMin, vec);

for (int i = 0; i < Vector<long>.Count; i++)
result = Math.Min(result, vecMin[i]);

var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}

foreach (var val in buffer)
result = Math.Min(result, val);

return minValue;
return result;
}

public static long Max(this RcCyclicBuffer<long> source)
private static long Max(this ReadOnlySpan<long> source)
{
if (0 >= source.Size)
return 0;
var buffer = source;
var result = long.MinValue;

long maxValue = long.MinValue;
foreach (var x in source)
if (Vector.IsHardwareAccelerated)
{
if (x > maxValue)
maxValue = x;
var vectors = MemoryMarshal.Cast<long, Vector<long>>(buffer);
var vecMax = Vector<long>.One * result;

foreach (var vec in vectors)
vecMax = Vector.Max(vecMax, vec);

for (int i = 0; i < Vector<long>.Count; i++)
result = Math.Max(result, vecMax[i]);

var remainder = source.Length % Vector<long>.Count;
buffer = buffer[^remainder..];
}

foreach (var val in buffer)
result = Math.Max(result, val);

return maxValue;
return result;
}

public static long Sum(this RcCyclicBuffer<long> source)
{
return Sum(source.ArrayOne()) + Sum(source.ArrayTwo());
}

public static double Average(this RcCyclicBuffer<long> source)
{
return Sum(source) / (double)source.Size;
}

public static long Min(this RcCyclicBuffer<long> source)
{
var firstHalf = source.ArrayOne();
var secondHalf = source.ArrayTwo();
var a = firstHalf.Length > 0 ? Min(firstHalf) : long.MaxValue;
var b = secondHalf.Length > 0 ? Min(secondHalf) : long.MaxValue;
return Math.Min(a, b);
}

public static long Max(this RcCyclicBuffer<long> source)
{
var firstHalf = source.ArrayOne();
var secondHalf = source.ArrayTwo();
var a = firstHalf.Length > 0 ? Max(firstHalf) : long.MinValue;
var b = secondHalf.Length > 0 ? Max(secondHalf) : long.MinValue;
return Math.Max(a, b);
}
}
}
116 changes: 116 additions & 0 deletions test/DotRecast.Core.Test/RcCyclicBufferTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,120 @@ public void RcCyclicBuffer_EnumeratorWorks()
Assert.That(enumerator.Current, Is.EqualTo(refValues[index++]));
}
}

[Test]
public void RcCyclicBuffers_Sum()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}

[Test]
public void RcCyclicBuffers_Average()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}

[Test]
public void RcCyclicBuffers_Min()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}

[Test]
public void RcCyclicBuffers_Max()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}

[Test]
public void RcCyclicBuffers_SumUnaligned()
{
var refValues = Enumerable.Range(-1, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}

[Test]
public void RcCyclicBuffers_AverageUnaligned()
{
var refValues = Enumerable.Range(-1, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}

[Test]
public void RcCyclicBuffers_MinUnaligned()
{
var refValues = Enumerable.Range(5, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}

[Test]
public void RcCyclicBuffers_MaxUnaligned()
{
var refValues = Enumerable.Range(-5, 3).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}

[Test]
public void RcCyclicBuffers_SumDeleted()
{
var initialValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var refValues = initialValues.Skip(1).SkipLast(1).ToArray();
var buffer = new RcCyclicBuffer<long>(initialValues.Length, initialValues);
buffer.PopBack();
buffer.PopFront();

Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}

[Test]
public void RcCyclicBuffers_SumSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Sum(buffer), Is.EqualTo(refValues.Sum()));
}

[Test]
public void RcCyclicBuffers_AverageSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Average(buffer), Is.EqualTo(refValues.Average()));
}

[Test]
public void RcCyclicBuffers_MinSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Min(buffer), Is.EqualTo(refValues.Min()));
}

[Test]
public void RcCyclicBuffers_MaxSplit()
{
var refValues = Enumerable.Range(-100, 211).Select(x => (long)x).ToArray();
var buffer = new RcCyclicBuffer<long>(refValues.Length, refValues);
buffer.PopFront();
buffer.PushBack(refValues[0]);
Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max()));
}
}
Loading