Skip to content

Commit

Permalink
Feature/stateless filtering and transforming (#823)
Browse files Browse the repository at this point in the history
* Fixed a build defect, present only for Debug builds, within Cache/TransformManyAsyncFixture

* Adjusted .editorconfig to allow for multi-line expressions to be passed as method parameters.

* Adjusted .editorconfig to re-allow omission of brackets for single-line code blocks.

* Consolidated language settings across all projects, and added polyfills to .Benchmarks, to match the other two.

* Added new operators `.FilterImmutable()` and `.TransformImmutable()` to provide better performance for static/deterministic scenarios.
  • Loading branch information
JakenVeina committed Jan 31, 2024
1 parent 38c6a38 commit d6d748e
Show file tree
Hide file tree
Showing 19 changed files with 1,400 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ dotnet_diagnostic.SA1114.severity = error
dotnet_diagnostic.SA1115.severity = error
dotnet_diagnostic.SA1116.severity = error
dotnet_diagnostic.SA1117.severity = error
dotnet_diagnostic.SA1118.severity = error
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1119.severity = error
dotnet_diagnostic.SA1120.severity = error
dotnet_diagnostic.SA1121.severity = error
Expand Down Expand Up @@ -415,7 +415,7 @@ dotnet_diagnostic.SA1413.severity = none
dotnet_diagnostic.SA1500.severity = error
dotnet_diagnostic.SA1501.severity = error
dotnet_diagnostic.SA1502.severity = error
dotnet_diagnostic.SA1503.severity = error
dotnet_diagnostic.SA1503.severity = none
dotnet_diagnostic.SA1504.severity = error
dotnet_diagnostic.SA1505.severity = error
dotnet_diagnostic.SA1506.severity = error
Expand Down
2 changes: 2 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;1701;1702;1705;VSX1000</NoWarn>
<Platform>AnyCPU</Platform>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<IsTestProject>$(MSBuildProjectName.Contains('Tests'))</IsTestProject>
<IsBenchmarkProject>$(MSBuildProjectName.Contains('Benchmarks'))</IsBenchmarkProject>
<DebugType>embedded</DebugType>
Expand Down
136 changes: 136 additions & 0 deletions src/DynamicData.Benchmarks/Cache/FilterImmutable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
public class FilterImmutable
{
private readonly IReadOnlyList<IChangeSet<Item, int>> _addChangeSets;
private readonly IReadOnlyList<IChangeSet<Item, int>> _replaceChangeSets;
private readonly IReadOnlyList<IChangeSet<Item, int>> _removeChangeSets;

public FilterImmutable()
{
var source = new ChangeAwareCache<Item, int>(capacity: 1_000);

var addChangeSets = new List<IChangeSet<Item, int>>(capacity: 1_000);
for (var id = 1; id <= 1_000; ++id)
{
source.Add(
item: new Item()
{
Id = id,
IsIncluded = (id % 2) == 0
},
key: id);
addChangeSets.Add(source.CaptureChanges());
}
_addChangeSets = addChangeSets;

var replaceChangeSets = new List<IChangeSet<Item, int>>(capacity: 500);
for (var id = 2; id <= 1_000; id += 2)
{
source.AddOrUpdate(
item: new Item()
{
Id = id,
IsIncluded = (id % 4) != 0
},
key: id);
replaceChangeSets.Add(source.CaptureChanges());
}
_replaceChangeSets = replaceChangeSets;

var removeChangeSets = new List<IChangeSet<Item, int>>(capacity: 1_000);
for (var id = 1; id <= 1_000; ++id)
{
source.Remove(id);
removeChangeSets.Add(source.CaptureChanges());
}
_removeChangeSets = removeChangeSets;
}

[Benchmark]
public void Adds()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _addChangeSets)
source.OnNext(changeSet);

source.OnCompleted();
}

[Benchmark]
public void AddsAndReplacements()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _addChangeSets)
source.OnNext(changeSet);

foreach (var changeSet in _replaceChangeSets)
source.OnNext(changeSet);

source.OnCompleted();
}

[Benchmark]
public void AddsAndRemoves()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _addChangeSets)
source.OnNext(changeSet);

foreach (var changeSet in _removeChangeSets)
source.OnNext(changeSet);

source.OnCompleted();
}

[Benchmark]
public void AddsReplacementsAndRemoves()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _addChangeSets)
source.OnNext(changeSet);

foreach (var changeSet in _replaceChangeSets)
source.OnNext(changeSet);

foreach (var changeSet in _removeChangeSets)
source.OnNext(changeSet);

source.OnCompleted();
}

private sealed class Item
{
public required int Id { get; init; }

public required bool IsIncluded { get; init; }
}
}
87 changes: 87 additions & 0 deletions src/DynamicData.Benchmarks/Cache/StatelessFiltering.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
public class StatelessFiltering
{
private readonly IReadOnlyList<IChangeSet<Item, int>> _changeSets;

public StatelessFiltering()
{
var source = new ChangeAwareCache<Item, int>(capacity: 1_000);
var changeSets = new List<IChangeSet<Item, int>>(capacity: 2_500);

for (var id = 1; id <= 1_000; ++id)
{
source.Add(
item: new Item()
{
Id = id,
IsIncluded = (id % 2) == 0
},
key: id);
changeSets.Add(source.CaptureChanges());
}

for (var id = 2; id <= 1_000; id += 2)
{
source.AddOrUpdate(
item: new Item()
{
Id = id,
IsIncluded = (id % 4) != 0
},
key: id);
changeSets.Add(source.CaptureChanges());
}

for (var id = 1; id <= 1_000; ++id)
{
source.Remove(id);
changeSets.Add(source.CaptureChanges());
}

_changeSets = changeSets;
}

[Benchmark(Baseline = true)]
public void Filter()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.Filter(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _changeSets)
source.OnNext(changeSet);
source.OnCompleted();
}

[Benchmark]
public void FilterImmutable()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.FilterImmutable(static item => item.IsIncluded)
.Subscribe();

foreach (var changeSet in _changeSets)
source.OnNext(changeSet);
source.OnCompleted();
}

private sealed class Item
{
public required int Id { get; init; }

public required bool IsIncluded { get; init; }
}
}
88 changes: 88 additions & 0 deletions src/DynamicData.Benchmarks/Cache/StatelessTransforming.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;

using BenchmarkDotNet.Attributes;

namespace DynamicData.Benchmarks.Cache;

[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
public class StatelessTransforming
{
private readonly IReadOnlyList<IChangeSet<Item, int>> _changeSets;

public StatelessTransforming()
{
var source = new ChangeAwareCache<Item, int>(capacity: 1_000);
var changeSets = new List<IChangeSet<Item, int>>(capacity: 2_500);

for (var id = 1; id <= 1_000; ++id)
{
source.Add(
item: new Item()
{
Id = id,
Name = $"Item #{id}"
},
key: id);
changeSets.Add(source.CaptureChanges());
}

for (var id = 2; id <= 1_000; id += 2)
{
source.AddOrUpdate(
item: new Item()
{
Id = id,
Name = $"Replacement Item #{id}"
},
key: id);
changeSets.Add(source.CaptureChanges());
}

for (var id = 1; id <= 1_000; ++id)
{
source.Remove(id);
changeSets.Add(source.CaptureChanges());
}

_changeSets = changeSets;
}

[Benchmark(Baseline = true)]
public void Transform()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.Transform(static item => item.Name)
.Subscribe();

foreach (var changeSet in _changeSets)
source.OnNext(changeSet);
source.OnCompleted();
}

[Benchmark]
public void TransformImmutable()
{
using var source = new Subject<IChangeSet<Item, int>>();

using var subscription = source
.TransformImmutable(static item => item.Name)
.Subscribe();

foreach (var changeSet in _changeSets)
source.OnNext(changeSet);
source.OnCompleted();
}

private sealed class Item
{
public required int Id { get; init; }

public required string Name { get; init; }
}
}

0 comments on commit d6d748e

Please sign in to comment.