diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs index a3671c3..41c5a92 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs @@ -262,6 +262,11 @@ namespace DotRecast.Core.Buffers return new Span(_buffer, 0, _end); } + internal ReadOnlySpan GetBufferSpan() + { + return _buffer; + } + public Enumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs index 5c8000c..71b88f7 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs @@ -1,20 +1,31 @@ -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 source) { - long sum = 0; - checked + var buffer = source.GetBufferSpan(); + var result = 0L; + if (Vector.IsHardwareAccelerated) { - // NOTE: SIMD would be nice here - foreach (var x in source) - { - sum += x; - } - } + var vectors = MemoryMarshal.Cast>(buffer); + var vecSum = Vector.Zero; + foreach (var vec in vectors) + vecSum += vec; - return sum; + result = Vector.Dot(vecSum, Vector.One); + var remainder = source.Size % Vector.Count; + buffer = buffer[^remainder..]; + } + + foreach (var val in buffer) + result += val; + + return result; } public static double Average(this RcCyclicBuffer source) @@ -27,32 +38,54 @@ public static long Min(this RcCyclicBuffer source) { - if (0 >= source.Size) - return 0; + var buffer = source.GetBufferSpan(); + 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>(buffer); + var vecMin = Vector.One * result; + + foreach (var vec in vectors) + vecMin = Vector.Min(vecMin, vec); - return minValue; + for (int i = 0; i < Vector.Count; i++) + result = Math.Min(result, vecMin[i]); + + var remainder = source.Size % Vector.Count; + buffer = buffer[^remainder..]; + } + + foreach (var val in buffer) + result = Math.Min(result, val); + + return result; } public static long Max(this RcCyclicBuffer source) { - if (0 >= source.Size) - return 0; + var buffer = source.GetBufferSpan(); + 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>(buffer); + var vecMax = Vector.One * result; + + foreach (var vec in vectors) + vecMax = Vector.Max(vecMax, vec); - return maxValue; + for (int i = 0; i < Vector.Count; i++) + result = Math.Max(result, vecMax[i]); + + var remainder = source.Size % Vector.Count; + buffer = buffer[^remainder..]; + } + + foreach (var val in buffer) + result = Math.Max(result, val); + + return result; } } } \ No newline at end of file diff --git a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs index 82f02fe..98f3f5c 100644 --- a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs +++ b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs @@ -330,4 +330,68 @@ public class RcCyclicBufferTests 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(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(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(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(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(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(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(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(refValues.Length, refValues); + Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max())); + } } \ No newline at end of file