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

CyclicBuffer optimizations #55

Merged
merged 1 commit into from
Feb 19, 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
63 changes: 45 additions & 18 deletions src/DotRecast.Core/Buffers/RcCyclicBuffer.cs
Original file line number Diff line number Diff line change
@@ -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<T>
public class RcCyclicBuffer<T> : IEnumerable<T>
{
public struct Enumerator : IEnumerator<T>
{
private readonly RcCyclicBuffer<T> _buffer;
private int _index;
private readonly int _size;

internal Enumerator(RcCyclicBuffer<T> 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;
Expand Down Expand Up @@ -155,29 +190,15 @@ public void Clear()

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<T> 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)
Expand Down Expand Up @@ -240,5 +261,11 @@ private Span<T> ArrayTwo()

return new Span<T>(_buffer, 0, _end);
}

public Enumerator GetEnumerator() => new Enumerator(this);

IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
14 changes: 9 additions & 5 deletions src/DotRecast.Core/Buffers/RcCyclicBuffers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ public static long Sum(this RcCyclicBuffer<long> source)
long sum = 0;
checked
{
source.ForEach(x => sum += x);
// NOTE: SIMD would be nice here
foreach (var x in source)
{
sum += x;
}
}

return sum;
Expand All @@ -27,11 +31,11 @@ public static long Min(this RcCyclicBuffer<long> source)
return 0;

long minValue = long.MaxValue;
source.ForEach(x =>
foreach (var x in source)
{
if (x < minValue)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use Math.Min to avoid branching
Also SIMD is applicable

minValue = x;
});
}

return minValue;
}
Expand All @@ -42,11 +46,11 @@ public static long Max(this RcCyclicBuffer<long> source)
return 0;

long maxValue = long.MinValue;
source.ForEach(x =>
foreach (var x in source)
{
if (x > maxValue)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use Math.Max to avoid branching
Also SIMD is applicable

maxValue = x;
});
}

return maxValue;
}
Expand Down
41 changes: 39 additions & 2 deletions test/DotRecast.Core.Test/RcCyclicBufferTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using DotRecast.Core.Buffers;
using DotRecast.Core.Collections;
using NUnit.Framework;

namespace DotRecast.Core.Test;
Expand Down Expand Up @@ -39,11 +40,11 @@ public void RcCyclicBuffer_GetEnumeratorConstructorDefinedArray_CorrectContent()
var buffer = new RcCyclicBuffer<int>(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]
Expand Down Expand Up @@ -290,4 +291,40 @@ public void RcCyclicBuffer_Clear_WorksNormallyAfterClear()
Assert.That(buffer[i], Is.EqualTo(i));
}
}

[Test]
public void RcCyclicBuffer_RegularForEachWorks()
{
var refValues = new[] { 4, 3, 2, 1, 0 };
var buffer = new RcCyclicBuffer<int>(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<int>(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++]));
}
}
}
Loading