From 43b5197f4f14c251e0ccba809cae4538fdc7292f Mon Sep 17 00:00:00 2001 From: wreng Date: Wed, 21 Feb 2024 16:06:00 +0300 Subject: [PATCH 1/2] CyclicBuffers SIMD --- src/DotRecast.Core/Buffers/RcCyclicBuffer.cs | 5 ++ src/DotRecast.Core/Buffers/RcCyclicBuffers.cs | 81 +++++++++++++------ .../DotRecast.Core.Test/RcCyclicBufferTest.cs | 64 +++++++++++++++ 3 files changed, 126 insertions(+), 24 deletions(-) diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs index a3671c31..41c5a92f 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs @@ -262,6 +262,11 @@ private Span ArrayTwo() 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 5c8000c2..71b88f70 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 double Average(this RcCyclicBuffer source) 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); + + 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 minValue; + 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); + + 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 maxValue; + 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 82f02fef..98f3f5c2 100644 --- a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs +++ b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs @@ -330,4 +330,68 @@ 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(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 From 2bf90af2f58410291a29f38cdc7a017b804a913f Mon Sep 17 00:00:00 2001 From: wreng Date: Wed, 21 Feb 2024 16:39:13 +0300 Subject: [PATCH 2/2] Support split special case --- src/DotRecast.Core/Buffers/RcCyclicBuffer.cs | 20 ++++--- src/DotRecast.Core/Buffers/RcCyclicBuffers.cs | 52 ++++++++++++++----- .../DotRecast.Core.Test/RcCyclicBufferTest.cs | 52 +++++++++++++++++++ 3 files changed, 101 insertions(+), 23 deletions(-) diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs index 41c5a92f..343e821b 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs @@ -191,12 +191,15 @@ public void Clear() public T[] ToArray() { T[] newArray = new T[Size]; + CopyTo(newArray); + return newArray; + } + public void CopyTo(Span 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.") @@ -232,7 +235,7 @@ private int InternalIndex(int index) : index - Capacity); } - private Span ArrayOne() + internal Span ArrayOne() { if (IsEmpty) { @@ -247,7 +250,7 @@ private Span ArrayOne() return new Span(_buffer, _start, _buffer.Length - _start); } - private Span ArrayTwo() + internal Span ArrayTwo() { if (IsEmpty) { @@ -262,11 +265,6 @@ private Span ArrayTwo() 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 71b88f70..fc942854 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs @@ -6,9 +6,9 @@ namespace DotRecast.Core.Buffers { public static class RcCyclicBuffers { - public static long Sum(this RcCyclicBuffer source) + public static long Sum(this ReadOnlySpan source) { - var buffer = source.GetBufferSpan(); + var buffer = source; var result = 0L; if (Vector.IsHardwareAccelerated) { @@ -18,7 +18,7 @@ public static long Sum(this RcCyclicBuffer source) vecSum += vec; result = Vector.Dot(vecSum, Vector.One); - var remainder = source.Size % Vector.Count; + var remainder = source.Length % Vector.Count; buffer = buffer[^remainder..]; } @@ -28,17 +28,17 @@ public static long Sum(this RcCyclicBuffer source) return result; } - public static double Average(this RcCyclicBuffer source) + public static double Average(this ReadOnlySpan 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 source) + private static long Min(this ReadOnlySpan source) { - var buffer = source.GetBufferSpan(); + var buffer = source; var result = long.MaxValue; if (Vector.IsHardwareAccelerated) @@ -52,7 +52,7 @@ public static long Min(this RcCyclicBuffer source) for (int i = 0; i < Vector.Count; i++) result = Math.Min(result, vecMin[i]); - var remainder = source.Size % Vector.Count; + var remainder = source.Length % Vector.Count; buffer = buffer[^remainder..]; } @@ -62,9 +62,9 @@ public static long Min(this RcCyclicBuffer source) return result; } - public static long Max(this RcCyclicBuffer source) + private static long Max(this ReadOnlySpan source) { - var buffer = source.GetBufferSpan(); + var buffer = source; var result = long.MinValue; if (Vector.IsHardwareAccelerated) @@ -78,7 +78,7 @@ public static long Max(this RcCyclicBuffer source) for (int i = 0; i < Vector.Count; i++) result = Math.Max(result, vecMax[i]); - var remainder = source.Size % Vector.Count; + var remainder = source.Length % Vector.Count; buffer = buffer[^remainder..]; } @@ -87,5 +87,33 @@ public static long Max(this RcCyclicBuffer source) return result; } + + public static long Sum(this RcCyclicBuffer source) + { + return Sum(source.ArrayOne()) + Sum(source.ArrayTwo()); + } + + public static double Average(this RcCyclicBuffer source) + { + return Sum(source) / (double)source.Size; + } + + public static long Min(this RcCyclicBuffer 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 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); + } } } \ No newline at end of file diff --git a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs index 98f3f5c2..df11f1de 100644 --- a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs +++ b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs @@ -394,4 +394,56 @@ public void RcCyclicBuffers_MaxUnaligned() var buffer = new RcCyclicBuffer(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(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(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(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(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(refValues.Length, refValues); + buffer.PopFront(); + buffer.PushBack(refValues[0]); + Assert.That(RcCyclicBuffers.Max(buffer), Is.EqualTo(refValues.Max())); + } } \ No newline at end of file