From 097a3655282846f4f8769e1db698e5506a5c954b Mon Sep 17 00:00:00 2001 From: wreng Date: Sun, 18 Feb 2024 20:37:11 +0300 Subject: [PATCH] CyclicBuffer optimizations --- src/DotRecast.Core/Buffers/RcCyclicBuffer.cs | 63 +++++++++++++------ src/DotRecast.Core/Buffers/RcCyclicBuffers.cs | 14 +++-- .../DotRecast.Core.Test/RcCyclicBufferTest.cs | 41 +++++++++++- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs index 2846bfb..65f9a91 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffer.cs @@ -1,12 +1,47 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Net.Security; namespace DotRecast.Core.Buffers { // https://github.com/joaoportela/CircularBuffer-CSharp/blob/master/CircularBuffer/CircularBuffer.cs - public class RcCyclicBuffer + public class RcCyclicBuffer : IEnumerable { + public struct Enumerator : IEnumerator + { + private readonly RcCyclicBuffer _buffer; + private int _index; + private readonly int _size; + + internal Enumerator(RcCyclicBuffer buffer) + { + _buffer = buffer; + _size = _buffer._size; + _index = default; + Reset(); + } + + public bool MoveNext() + { + return ++_index < _size; + } + + public void Reset() + { + _index = -1; + } + + public T Current => _buffer[_index]; + + object IEnumerator.Current => Current; + + public void Dispose() + { + // This could be used to unlock write access to collection + } + } + private readonly T[] _buffer; private int _start; @@ -155,29 +190,15 @@ namespace DotRecast.Core.Buffers public T[] ToArray() { - int idx = 0; T[] newArray = new T[Size]; - ForEach(x => newArray[idx++] = x); + var span1 = ArrayOne(); + span1.CopyTo(newArray.AsSpan()); + ArrayTwo().CopyTo(newArray.AsSpan(span1.Length..)); return newArray; } - public void ForEach(Action action) - { - var spanOne = ArrayOne(); - foreach (var item in spanOne) - { - action.Invoke(item); - } - - var spanTwo = ArrayTwo(); - foreach (var item in spanTwo) - { - action.Invoke(item); - } - } - private void ThrowIfEmpty(string message = "Cannot access an empty buffer.") { if (IsEmpty) @@ -240,5 +261,11 @@ namespace DotRecast.Core.Buffers return new Span(_buffer, 0, _end); } + + public Enumerator GetEnumerator() => new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } \ No newline at end of file diff --git a/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs b/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs index cadcf48..5c8000c 100644 --- a/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs +++ b/src/DotRecast.Core/Buffers/RcCyclicBuffers.cs @@ -7,7 +7,11 @@ long sum = 0; checked { - source.ForEach(x => sum += x); + // NOTE: SIMD would be nice here + foreach (var x in source) + { + sum += x; + } } return sum; @@ -27,11 +31,11 @@ return 0; long minValue = long.MaxValue; - source.ForEach(x => + foreach (var x in source) { if (x < minValue) minValue = x; - }); + } return minValue; } @@ -42,11 +46,11 @@ return 0; long maxValue = long.MinValue; - source.ForEach(x => + foreach (var x in source) { if (x > maxValue) maxValue = x; - }); + } return maxValue; } diff --git a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs index 56a9c54..9c72e51 100644 --- a/test/DotRecast.Core.Test/RcCyclicBufferTest.cs +++ b/test/DotRecast.Core.Test/RcCyclicBufferTest.cs @@ -1,5 +1,6 @@ using System; using DotRecast.Core.Buffers; +using DotRecast.Core.Collections; using NUnit.Framework; namespace DotRecast.Core.Test; @@ -39,11 +40,11 @@ public class RcCyclicBufferTests var buffer = new RcCyclicBuffer(5, new[] { 0, 1, 2, 3 }); int x = 0; - buffer.ForEach(item => + foreach (var item in buffer) { Assert.That(item, Is.EqualTo(x)); x++; - }); + } } [Test] @@ -290,4 +291,40 @@ public class RcCyclicBufferTests Assert.That(buffer[i], Is.EqualTo(i)); } } + + [Test] + public void RcCyclicBuffer_RegularForEachWorks() + { + var refValues = new[] { 4, 3, 2, 1, 0 }; + var buffer = new RcCyclicBuffer(5, refValues); + + var index = 0; + foreach (var element in buffer) + { + Assert.That(element, Is.EqualTo(refValues[index++])); + } + } + + [Test] + public void RcCyclicBuffer_EnumeratorWorks() + { + var refValues = new[] { 4, 3, 2, 1, 0 }; + var buffer = new RcCyclicBuffer(5, refValues); + + var index = 0; + var enumerator = buffer.GetEnumerator(); + enumerator.Reset(); + while (enumerator.MoveNext()) + { + Assert.That(enumerator.Current, Is.EqualTo(refValues[index++])); + } + + // Ensure Reset works properly + index = 0; + enumerator.Reset(); + while (enumerator.MoveNext()) + { + Assert.That(enumerator.Current, Is.EqualTo(refValues[index++])); + } + } } \ No newline at end of file