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

Rewrite TripleBuffer as a true lockless, flipping, triple buffer #6319

Merged
merged 12 commits into from
Jun 28, 2024
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
Loading