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

New SortAndBind operator #878

Merged
merged 11 commits into from
Mar 20, 2024
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ dotnet_diagnostic.SA1619.severity = error
dotnet_diagnostic.SA1620.severity = error
dotnet_diagnostic.SA1621.severity = error
dotnet_diagnostic.SA1622.severity = error
dotnet_diagnostic.SA1623.severity = error
dotnet_diagnostic.SA1623.severity = none
dotnet_diagnostic.SA1624.severity = error
dotnet_diagnostic.SA1625.severity = error
dotnet_diagnostic.SA1626.severity = error
Expand Down Expand Up @@ -495,7 +495,7 @@ dotnet_diagnostic.RCS1242.severity=error
dotnet_diagnostic.CA2016.severity=warning
dotnet_diagnostic.CA2014.severity=error
dotnet_diagnostic.RCS1010.severity=error
dotnet_diagnostic.RCS1006.severity=error
dotnet_diagnostic.RCS1006.severity=suggestion
dotnet_diagnostic.RCS1005.severity=error
dotnet_diagnostic.RCS1020.severity=error
dotnet_diagnostic.RCS1049.severity=warning
Expand Down
111 changes: 111 additions & 0 deletions src/DynamicData.Benchmarks/Cache/SortAndBindChange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using BenchmarkDotNet.Attributes;
using DynamicData.Binding;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
public class SortAndBindChange: IDisposable
{
private readonly Random _random = new();
private record Item(string Name, int Id, int Ranking);

private readonly SortExpressionComparer<Item> _comparer = SortExpressionComparer<Item>
.Ascending(i => i.Ranking)
.ThenByAscending(i => i.Name);


Subject<IChangeSet<Item, int>> _newSubject = new();
Subject<IChangeSet<Item, int>> _newSubjectOptimised = new();
Subject<IChangeSet<Item, int>> _oldSubject = new();
Subject<IChangeSet<Item, int>> _oldSubjectOptimised = new();

private IDisposable? _cleanUp;

private ReadOnlyObservableCollection<Item>? _newList;
private ReadOnlyObservableCollection<Item>? _newListOptimised;
private ReadOnlyObservableCollection<Item>? _oldList;
private ReadOnlyObservableCollection<Item>? _oldListOptimised;



[Params(10, 100, 1_000, 10_000, 50_000)]
public int Count { get; set; }


[GlobalSetup]
public void SetUp()
{
_oldSubject = new Subject<IChangeSet<Item, int>>();
_oldSubjectOptimised = new Subject<IChangeSet<Item, int>>();
_newSubject = new Subject<IChangeSet<Item, int>>();
_newSubjectOptimised = new Subject<IChangeSet<Item, int>>();


_cleanUp = new CompositeDisposable
(
_newSubject.SortAndBind(out var newList, _comparer).Subscribe(),
_newSubjectOptimised.SortAndBind(out var optimisedList, _comparer, new SortAndBindOptions
{
InitialCapacity = Count,
UseBinarySearch = true
}).Subscribe(),

_oldSubject.Sort(_comparer).Bind(out var oldList).Subscribe(),
_oldSubjectOptimised.Sort(_comparer, SortOptimisations.ComparesImmutableValuesOnly).Bind(out var oldOptimisedList).Subscribe()
);

_newList = newList;
_newListOptimised = optimisedList;
_oldList = oldList;
_oldListOptimised = oldOptimisedList;



var changeSet = new ChangeSet<Item, int>(Count);
foreach (var i in Enumerable.Range(1, Count))
{
var item = new Item($"Item{i}", i, _random.Next(1, 1000));
changeSet.Add(new Change<Item, int>(ChangeReason.Add, i, item));
}

_newSubject.OnNext(changeSet);
_newSubjectOptimised.OnNext(changeSet);
_oldSubject.OnNext(changeSet);
_oldSubjectOptimised.OnNext(changeSet);

}

[Benchmark(Baseline = true)]
public void Old() => RunTest(_oldSubject, _oldList!);


[Benchmark]
public void OldOptimized() => RunTest(_oldSubjectOptimised, _oldListOptimised!);

[Benchmark]
public void New() => RunTest(_newSubject, _newList!);

[Benchmark]
public void NewOptimized() => RunTest(_newSubjectOptimised, _newListOptimised!);


void RunTest(Subject<IChangeSet<Item, int>> subject, ReadOnlyObservableCollection<Item> list)
{
var original = list[Count / 2];
var updated = original with { Ranking = _random.Next(1, 1000) };

subject.OnNext(new ChangeSet<Item, int>
{
new(ChangeReason.Update, original.Id, updated, original)
});
}


public void Dispose() => _cleanUp?.Dispose();
}
81 changes: 81 additions & 0 deletions src/DynamicData.Benchmarks/Cache/SortAndBindInitial.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using DynamicData.Binding;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
public class SortAndBindInitial: IDisposable
{
private readonly Random _random = new();

private record Item(string Name, int Id, int Ranking);

private readonly SortExpressionComparer<Item> _comparer = SortExpressionComparer<Item>.Ascending(i => i.Ranking).ThenByAscending(i => i.Name);


Subject<IChangeSet<Item, int>> _newSubject = new();
Subject<IChangeSet<Item, int>> _newSubjectOptimised = new();
Subject<IChangeSet<Item, int>> _oldSubject = new();
Subject<IChangeSet<Item, int>> _oldSubjectOptimised = new();

private IDisposable? _cleanUp;
private ChangeSet<Item, int>? _changeSet;


[Params(10, 100, 1_000, 10_000, 50_000)]
public int Count { get; set; }


[GlobalSetup]
public void SetUp()
{
_oldSubject = new Subject<IChangeSet<Item, int>>();
_oldSubjectOptimised = new Subject<IChangeSet<Item, int>>();
_newSubject = new Subject<IChangeSet<Item, int>>();
_newSubjectOptimised = new Subject<IChangeSet<Item, int>>();

var changeSet = new ChangeSet<Item, int>(Count);
foreach (var i in Enumerable.Range(1, Count))
{
var item = new Item($"Item{i}", i, _random.Next(1, 1000));
changeSet.Add(new Change<Item, int>(ChangeReason.Add, i, item));
}

_changeSet = changeSet;

_cleanUp = new CompositeDisposable
(
_newSubject.SortAndBind(out var newList, _comparer).Subscribe(),
_newSubjectOptimised.SortAndBind(out var optimisedList, _comparer, new SortAndBindOptions
{
InitialCapacity = Count,
UseBinarySearch = true
}).Subscribe(),

_oldSubject.Sort(_comparer).Bind(out var oldList).Subscribe(),
_oldSubjectOptimised.Sort(_comparer, SortOptimisations.ComparesImmutableValuesOnly).Bind(out var oldOptimisedList).Subscribe()

);
}


[Benchmark(Baseline = true)]
public void Old() => _oldSubject.OnNext(_changeSet!);

[Benchmark]
public void OldOptimized() => _oldSubjectOptimised.OnNext(_changeSet!);

[Benchmark]
public void New() => _newSubject.OnNext(_changeSet!);

[Benchmark]
public void NewOptimized() => _newSubjectOptimised.OnNext(_changeSet!);


public void Dispose() => _cleanUp?.Dispose();
}
3 changes: 3 additions & 0 deletions src/DynamicData.Benchmarks/DynamicData.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@
<ItemGroup>
<None Include="BenchmarkDotNet.Artifacts\**\*" />
</ItemGroup>
<ItemGroup>
<Folder Include="BenchmarkDotNet.Artifacts\" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/DynamicData.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ public static void Main(string[] args)
private static string GetProjectRootDirectory([CallerFilePath] string? callerFilePath = null)
=> Path.GetDirectoryName(callerFilePath)!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,14 @@ namespace DynamicData.Binding
public static bool operator !=(DynamicData.Binding.PropertyValue<TObject, TValue>? left, DynamicData.Binding.PropertyValue<TObject, TValue>? right) { }
public static bool operator ==(DynamicData.Binding.PropertyValue<TObject, TValue>? left, DynamicData.Binding.PropertyValue<TObject, TValue>? right) { }
}
public struct SortAndBindOptions : System.IEquatable<DynamicData.Binding.SortAndBindOptions>
{
public SortAndBindOptions() { }
public int InitialCapacity { get; init; }
public int ResetThreshold { get; init; }
public bool UseBinarySearch { get; init; }
public bool UseReplaceForUpdates { get; init; }
}
public enum SortDirection
{
Ascending = 0,
Expand Down Expand Up @@ -700,6 +708,7 @@ namespace DynamicData
public static class DynamicDataOptions
{
public static DynamicData.Binding.BindingOptions Binding { get; set; }
public static DynamicData.Binding.SortAndBindOptions SortAndBind { get; set; }
}
public static class EnumerableEx
{
Expand Down Expand Up @@ -1751,6 +1760,18 @@ namespace DynamicData
public static System.IObservable<DynamicData.ISortedChangeSet<TObject, TKey>> Sort<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, System.IObservable<System.Collections.Generic.IComparer<TObject>> comparerObservable, System.IObservable<System.Reactive.Unit> resorter, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> SortAndBind<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, System.Collections.Generic.IList<TObject> targetList, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> SortAndBind<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection<TObject> readOnlyObservableCollection, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> SortAndBind<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, System.Collections.Generic.IList<TObject> targetList, System.Collections.Generic.IComparer<TObject> comparer, DynamicData.Binding.SortAndBindOptions options)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> SortAndBind<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, out System.Collections.ObjectModel.ReadOnlyObservableCollection<TObject> readOnlyObservableCollection, System.Collections.Generic.IComparer<TObject> comparer, DynamicData.Binding.SortAndBindOptions options)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.ISortedChangeSet<TObject, TKey>> SortBy<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, System.Func<TObject, System.IComparable> expression, DynamicData.Binding.SortDirection sortOrder = 0, DynamicData.SortOptimisations sortOptimisations = 0, int resetThreshold = 100)
where TObject : notnull
where TKey : notnull { }
Expand Down
Loading