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

Improve BindableList performance #6405

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
73 changes: 55 additions & 18 deletions osu.Framework.Benchmarks/BenchmarkBindableList.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,87 @@
// 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.Linq;
using BenchmarkDotNet.Attributes;
using osu.Framework.Bindables;

namespace osu.Framework.Benchmarks
{
[MemoryDiagnoser]
[WarmupCount(0)]
[IterationCount(2)]
public class BenchmarkBindableList
{
private readonly BindableList<int> list = new BindableList<int>();
private IBindableList<int> iList => list;
private static readonly int[] small_data = Enumerable.Range(0, 10).ToArray();

[Params(0, 1, 10, 20)]
public int NumBindings { get; set; }

private BindableList<int>[] lists = null!;

[GlobalSetup]
public void GlobalSetup()
{
for (int i = 0; i < 10; i++)
list.Add(i);
lists = new BindableList<int>[NumBindings + 1];

lists[0] = new BindableList<int>(Enumerable.Range(0, 10000).ToArray());
for (int i = 1; i < lists.Length; i++)
lists[i] = lists[i - 1].GetBoundCopy();
lists[0].Clear();
}

[Benchmark(Baseline = true)]
public void Create()
{
setupList();
}

[Benchmark]
public int Enumerate()
public void Add()
{
int result = 0;
setupList().Add(1);
}

for (int i = 0; i < 100; i++)
{
foreach (int val in list)
result += val;
}
[Benchmark]
public void Remove()
{
setupList().Remove(0);
}

return result;
[Benchmark]
public void Clear()
{
setupList().Clear();
}

[Benchmark]
public int EnumerateInterface()
public void AddRange()
{
setupList().AddRange(small_data);
}

[Benchmark]
public void SetIndex()
{
setupList()[0]++;
}

[Benchmark]
public int Enumerate()
{
int result = 0;

for (int i = 0; i < 100; i++)
{
foreach (int val in iList)
result += val;
}
foreach (int val in setupList())
result += val;

return result;
}

private BindableList<int> setupList()
{
lists[0].Clear();
lists[0].Add(0);
return lists[0];
}
}
}
67 changes: 67 additions & 0 deletions osu.Framework/Bindables/BindableInstanceTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace osu.Framework.Bindables
{
/// <summary>
/// Facilitates tracking of bindable instances through recursive invocations on bound bindables.
/// Can be provided a small array for fast/naive de-duplication while falling back to <see cref="HashSet{T}"/> when there are too many bindable instances.
/// </summary>
internal ref struct BindableInstanceTracker
{
private readonly Span<int> fastStorage;
private HashSet<int>? slowStorage;
private int count;

/// <summary>
/// Creates a bindable instance tracker.
/// </summary>
/// <param name="fastStorage">A small array for far de-duplication. Usually allocated on the stack.</param>
public BindableInstanceTracker(Span<int> fastStorage)
{
this.fastStorage = fastStorage;
}

/// <summary>
/// Adds an instance to the tracker.
/// </summary>
/// <param name="id">A unique instance ID.</param>
/// <returns>Whether the instance was previously seen.</returns>
public bool Add(int id)
{
Debug.Assert(id != 0);

bool result = count < fastStorage.Length ? addFast(id) : addSlow(id);
count += result ? 1 : 0;
return result;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool addSlow(int id)
{
if (slowStorage == null)
{
slowStorage = [];
for (int i = 0; i < count; i++)
slowStorage.Add(fastStorage[i]);
}

return slowStorage.Add(id);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool addFast(int id)
{
if (fastStorage[..count].Contains(id))
return false;

fastStorage[count] = id;
return true;
}
}
}
Loading
Loading