recalculate every nth

This commit is contained in:
mischa 2023-07-24 22:12:45 +08:00
parent 9cc3b32730
commit 61243a9fbd
2 changed files with 85 additions and 10 deletions

View File

@ -17,14 +17,21 @@ public class HistoryBounds
// history limit. oldest bounds will be removed. // history limit. oldest bounds will be removed.
public readonly int limit; public readonly int limit;
// only remove old entries every n-th insertion.
// new entries are still encapsulated on every insertion.
// for example, every 2nd insertion is enough, and 2x as fast.
public readonly int recalculateEveryNth;
int recalculateCounter = 0;
// total bounds encapsulating all of the bounds history // total bounds encapsulating all of the bounds history
public Bounds total; public Bounds total;
public HistoryBounds(int limit) public HistoryBounds(int limit, int recalculateEveryNth)
{ {
// initialize queue with maximum capacity to avoid runtime resizing // initialize queue with maximum capacity to avoid runtime resizing
// +1 because it makes the code easier if we insert first, and then remove. // +1 because it makes the code easier if we insert first, and then remove.
this.limit = limit; this.limit = limit;
this.recalculateEveryNth = recalculateEveryNth;
history = new Queue<Bounds>(limit + 1); history = new Queue<Bounds>(limit + 1);
} }
@ -47,6 +54,14 @@ public void Insert(Bounds bounds)
// remove oldest // remove oldest
history.Dequeue(); history.Dequeue();
// optimization: only recalculate every n-th removal.
// accurate enough, and N times faster.
if (++recalculateCounter < recalculateEveryNth)
return;
// reset counter
recalculateCounter = 0;
// recalculate total bounds // recalculate total bounds
// (only needed after removing the oldest) // (only needed after removing the oldest)
total = bounds; total = bounds;

View File

@ -23,12 +23,13 @@ public static Bounds MinMax(float min, float max) =>
// 64 entries are much more than we would usually use. // 64 entries are much more than we would usually use.
// //
// Unity 2021.3 LTS, release mode: 10x000 x 65; limit=8 // Unity 2021.3 LTS, release mode: 10x000 x 65; limit=8
// O(N) Queue<Bounds> implementation: 1045 ms // O(N) Queue<Bounds> implementation: 1045 ms
// O(N) Queue and recalculate every 2nd: 640 ms
[Test] [Test]
[TestCase(10_000, 64, 8)] [TestCase(10_000, 64, 8, 2)]
public void Benchmark(int iterations, int insertions, int limit) public void Benchmark(int iterations, int insertions, int limit, int recalculate)
{ {
HistoryBounds history = new HistoryBounds(limit); HistoryBounds history = new HistoryBounds(limit, recalculate);
// always use the same seed so we get the same test. // always use the same seed so we get the same test.
Random.InitState(0); Random.InitState(0);
@ -58,7 +59,7 @@ public void Benchmark(int iterations, int insertions, int limit)
public void Insert_Basic() public void Insert_Basic()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit); HistoryBounds history = new HistoryBounds(limit, recalculateEveryNth: 1);
// insert initial [-1, 1]. // insert initial [-1, 1].
// should calculate new bounds == initial. // should calculate new bounds == initial.
@ -93,7 +94,7 @@ public void Insert_Basic()
public void Insert_Revisit() public void Insert_Revisit()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit); HistoryBounds history = new HistoryBounds(limit, recalculateEveryNth: 1);
// insert initial [-1, 1]. // insert initial [-1, 1].
// should calculate new bounds == initial. // should calculate new bounds == initial.
@ -123,10 +124,10 @@ public void Insert_Revisit()
// by default, HistoryBounds.total is new Bounds() which is (0,0). // by default, HistoryBounds.total is new Bounds() which is (0,0).
// make sure this isn't included in results by default. // make sure this isn't included in results by default.
[Test] [Test]
public void InsertFar() public void Insert_Far()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit); HistoryBounds history = new HistoryBounds(limit, recalculateEveryNth: 1);
// insert initial [2, 3]. // insert initial [2, 3].
// should calculate new bounds == initial. // should calculate new bounds == initial.
@ -154,12 +155,71 @@ public void InsertFar()
Assert.That(history.Count, Is.EqualTo(3)); Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(0.5f, 4))); Assert.That(history.total, Is.EqualTo(MinMax(0.5f, 4)));
} }
// test to check if recalculate works as expected
[Test]
public void Insert_Recalculate()
{
const int limit = 3;
HistoryBounds history = new HistoryBounds(limit, recalculateEveryNth: 2);
// insert initial [-1, 1].
// should calculate new bounds == initial.
history.Insert(MinMax(-1, 1));
Assert.That(history.Count, Is.EqualTo(1));
Assert.That(history.total, Is.EqualTo(MinMax(-1, 1)));
// insert [0, 2]
// should calculate new bounds == [-1, 2].
history.Insert(MinMax(0, 2));
Assert.That(history.Count, Is.EqualTo(2));
Assert.That(history.total, Is.EqualTo(MinMax(-1, 2)));
// insert one that's smaller than current bounds [-.5, 0]
// history needs to contain it even if smaller, because once the oldest
// largest one gets removed, this one matters too.
history.Insert(MinMax(-0.5f, 1));
Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(-1, 2)));
// insert more than 'limit': [0, 0]
// the oldest one [-1, 1] should be discarded.
// recalculate counter is 0+=1. nothing is recalculated yet.
history.Insert(MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(-1, 2)));
// insert more than 'limit': [0, 0]
// the oldest one [0, 2] should be discarded.
// recalculate counter is 1+=1. new bounds are recalculated.
// recalculate counter should be reset to 0.
history.Insert(MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(-0.5f, 1)));
// ... test another iteration to ensure recalculate counter was
// reset properly ...
// insert more than 'limit': [0, 0]
// the oldest one [-0.5, 1] should be discarded.
// recalculate counter is 0+=1. nothing is recalculated yet.
history.Insert(MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(-0.5f, 1)));
// insert more than 'limit': [0, 0]
// the oldest one [0, 0] should be discarded.
// recalculate counter is 1+=1. new bounds are recalculated.
// recalculate counter should be reset to 0.
history.Insert(MinMax(0, 0));
Assert.That(history.Count, Is.EqualTo(3));
Assert.That(history.total, Is.EqualTo(MinMax(0, 0)));
}
[Test] [Test]
public void Reset() public void Reset()
{ {
const int limit = 3; const int limit = 3;
HistoryBounds history = new HistoryBounds(limit); HistoryBounds history = new HistoryBounds(limit, recalculateEveryNth: 1);
history.Insert(MinMax(1, 2)); history.Insert(MinMax(1, 2));
history.Insert(MinMax(2, 3)); history.Insert(MinMax(2, 3));