Skip to content

Commit

Permalink
New SortAndBind operator (#878)
Browse files Browse the repository at this point in the history
* BindAndSort operator
  • Loading branch information
RolandPheasant committed Mar 20, 2024
1 parent e8c905a commit ba58742
Show file tree
Hide file tree
Showing 13 changed files with 1,085 additions and 6 deletions.
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

0 comments on commit ba58742

Please sign in to comment.