Skip to content

Commit

Permalink
Merge pull request #6319 from smoogipoo/triple-buffer-rewrite
Browse files Browse the repository at this point in the history
Rewrite TripleBuffer as a true lockless, flipping, triple buffer
  • Loading branch information
peppy authored Jun 28, 2024
2 parents 813a4a6 + 1823132 commit b9cbabc
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 309 deletions.
199 changes: 26 additions & 173 deletions osu.Framework.Tests/Graphics/TripleBufferTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,196 +40,49 @@ public void TestSameBufferIsNotWrittenTwiceInRowNoContestation()
{
var tripleBuffer = createWithIDsMatchingIndices();

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(0));
int? lastWrite = null;

// buffer 0: waiting for read
// buffer 1: old
// buffer 2: old

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer?.Object?.ID, Is.EqualTo(0));

// buffer 0: last read
// buffer 1: old
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: last read
// buffer 1: waiting for read
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(2));

// buffer 0: last read
// buffer 1: old
// buffer 2: waiting for read

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: last read
// buffer 1: waiting for read
// buffer 2: old

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer?.Object?.ID, Is.EqualTo(1));

// buffer 0: old
// buffer 1: last read
// buffer 2: old
}

[Test]
public void TestSameBufferIsNotWrittenTwiceInRowContestation()
{
var tripleBuffer = createWithIDsMatchingIndices();

// Test with first write in use during second.
using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(0));

// buffer 0: waiting for read
// buffer 1: old
// buffer 2: old

using (var read = tripleBuffer.GetForRead())
{
Assert.That(read?.Object?.ID, Is.EqualTo(0));

// buffer 0: reading
// buffer 1: old
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: reading
// buffer 1: waiting for read
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(2));

// buffer 0: reading
// buffer 1: old
// buffer 2: waiting for read
}

using (var read = tripleBuffer.GetForRead())
for (int i = 0; i < 3; i++)
{
Assert.That(read?.Object?.ID, Is.EqualTo(2));

// buffer 0: old
// buffer 1: old
// buffer 2: reading

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(0));

// buffer 0: waiting for read
// buffer 1: old
// buffer 2: reading

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: old
// buffer 1: waiting for read
// buffer 2: reading

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(0));

// buffer 0: waiting for read
// buffer 1: old
// buffer 2: reading
}
{
Assert.That(write.Object!.ID, Is.Not.EqualTo(lastWrite));
lastWrite = write.Object!.ID;
}

using (var read = tripleBuffer.GetForRead())
{
Assert.That(read?.Object?.ID, Is.EqualTo(0));
// buffer 0: reading
// buffer 1: old
// buffer 2: old
using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer!.Object!.ID, Is.EqualTo(lastWrite));
}
}

[Test]
public void TestSameBufferIsNotWrittenTwiceInRowContestation2()
public void TestSameBufferIsNotWrittenTwiceInRowContestation()
{
var tripleBuffer = createWithIDsMatchingIndices();

using (var write = tripleBuffer.GetForWrite())
Assert.That(write.Object?.ID, Is.EqualTo(0));

// buffer 0: waiting for read
// buffer 1: old
// buffer 2: old

using (var read = tripleBuffer.GetForRead())
{
Assert.That(read?.Object?.ID, Is.EqualTo(0));

// buffer 0: reading
// buffer 1: old
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
{
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: reading
// buffer 1: writing
// buffer 2: old
}
}

using (var read = tripleBuffer.GetForRead())
// Test with first write in use during second.
using (tripleBuffer.GetForWrite())
{
Assert.That(read?.Object?.ID, Is.EqualTo(1));

// buffer 0: old
// buffer 1: reading
// buffer 2: old
}

using (var write = tripleBuffer.GetForWrite())
{
Assert.That(write.Object?.ID, Is.EqualTo(0));

// buffer 0: writing
// buffer 1: last read
// buffer 2: old
}
int? lastRead = null;
int? lastWrite = null;

using (var read = tripleBuffer.GetForRead())
for (int i = 0; i < 3; i++)
{
Assert.That(read?.Object?.ID, Is.EqualTo(0));

// buffer 0: reading
// buffer 1: old
// buffer 2: old

using (var write = tripleBuffer.GetForWrite())
using (var read = tripleBuffer.GetForRead())
{
Assert.That(write.Object?.ID, Is.EqualTo(1));

// buffer 0: reading
// buffer 1: writing
// buffer 2: old
}

using (var write = tripleBuffer.GetForWrite())
{
Assert.That(write.Object?.ID, Is.EqualTo(2));

// buffer 0: reading
// buffer 1: waiting for read
// buffer 2: writing
Assert.That(read!.Object!.ID, Is.Not.EqualTo(lastRead));

for (int j = 0; j < 3; j++)
{
using (var write = tripleBuffer.GetForWrite())
{
Assert.That(write.Object!.ID, Is.Not.EqualTo(lastWrite));
Assert.That(write.Object!.ID, Is.Not.EqualTo(read.Object?.ID));
lastWrite = write.Object!.ID;
}
}
}
}
}
Expand Down
124 changes: 124 additions & 0 deletions osu.Framework.Tests/Visual/Graphics/TestSceneTripleBufferOccupancy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;

namespace osu.Framework.Tests.Visual.Graphics
{
public partial class TestSceneTripleBufferOccupancy : FrameworkTestScene
{
private readonly CancellationTokenSource cts = new CancellationTokenSource();

private readonly TextFlowContainer text;

private long[] writes = new long[3];
private long[] reads = new long[3];
private Stopwatch stopwatch = Stopwatch.StartNew();

private int writeLag;
private int readLag;

public TestSceneTripleBufferOccupancy()
{
Add(text = new TextFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both
});
}

protected override void LoadComplete()
{
base.LoadComplete();

TripleBuffer<object> tripleBuffer = new TripleBuffer<object>();

new Thread(() =>
{
while (!cts.IsCancellationRequested)
{
using (var write = tripleBuffer.GetForWrite())
writes[write.Index]++;
if (writeLag != 0)
Thread.Sleep(writeLag);
}
}).Start();

new Thread(() =>
{
while (!cts.IsCancellationRequested)
{
using (var read = tripleBuffer.GetForRead())
{
if (read != null)
reads[read.Index]++;
}
if (readLag != 0)
Thread.Sleep(readLag);
}
}).Start();

AddSliderStep("write lag", 0, 16, 0, v =>
{
writeLag = v;
reset();
});

AddSliderStep("read lag", 0, 16, 0, v =>
{
readLag = v;
reset();
});

reset();
}

private void reset()
{
writes = new long[3];
reads = new long[3];
stopwatch = Stopwatch.StartNew();
}

protected override void Update()
{
base.Update();

StringBuilder info = new StringBuilder();

double totalWrites = writes.Sum();
double totalReads = reads.Sum();

info.AppendLine("write occupancy:");
for (int i = 0; i < writes.Length; i++)
info.AppendLine($"{i}: {writes[i] / totalWrites,-10:P}({writes[i]} / {totalWrites})");

info.AppendLine();

info.AppendLine("read occupancy:");
for (int i = 0; i < reads.Length; i++)
info.AppendLine($"{i}: {reads[i] / totalReads,-10:P}({reads[i]} / {totalReads})");

info.AppendLine();

info.AppendLine($"Speed: {stopwatch.Elapsed.TotalMicroseconds / totalReads:0.00}us/read");

text.Text = info.ToString();
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
cts.Cancel();
}
}
}
40 changes: 0 additions & 40 deletions osu.Framework/Allocation/ObjectUsage.cs

This file was deleted.

Loading

0 comments on commit b9cbabc

Please sign in to comment.