From 610bf06f10220be94154d9a508da8b27530f1c88 Mon Sep 17 00:00:00 2001
From: Colt <6819362+cabauman@users.noreply.github.com>
Date: Sun, 13 Dec 2020 11:07:06 +0900
Subject: [PATCH] feat: generate benchmarks via t4 (#105)
Co-authored-by: Glenn <5834289+glennawatson@users.noreply.github.com>
---
.../BindBenchmarks.cs | 270 +++++++++++++-----
.../BindBenchmarks.tt | 133 +++++++++
.../PropertyChangesBenchmarks.cs | 145 ----------
...eMarbles.PropertyChanged.Benchmarks.csproj | 31 +-
.../WhenChangedBenchmarks.cs | 267 +++++++++++++++++
.../WhenChangedBenchmarks.tt | 129 +++++++++
6 files changed, 757 insertions(+), 218 deletions(-)
create mode 100644 src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.tt
delete mode 100644 src/ReactiveMarbles.PropertyChanged.Benchmarks/PropertyChangesBenchmarks.cs
create mode 100644 src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.cs
create mode 100644 src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.tt
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.cs b/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.cs
index 5b44e06c..beae03dc 100644
--- a/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.cs
+++ b/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.cs
@@ -1,24 +1,21 @@
-// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
+// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
+using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
+
using ReactiveMarbles.PropertyChanged.Benchmarks.Moqs;
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq.Expressions;
-using New = ReactiveMarbles.PropertyChanged.BindExtensions;
-using Old = ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.BindExtensions;
using UI = ReactiveUI.PropertyBindingMixins;
-
-#nullable disable
+using Old = ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.BindExtensions;
+using New = ReactiveMarbles.PropertyChanged.BindExtensions;
namespace ReactiveMarbles.PropertyChanged.Benchmarks
{
///
- /// Benchmarks for the property changed.
+ /// Benchmarks for binding.
///
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[MemoryDiagnoser]
@@ -26,40 +23,41 @@ namespace ReactiveMarbles.PropertyChanged.Benchmarks
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class BindBenchmarks
{
+ private TestClass _from;
+ private TestClass _to;
+ private IDisposable _binding;
///
- /// The parameters for the tests.
+ /// The number mutations to perform.
///
- [SuppressMessage("Design", "SA1401: field should be private", Justification = "needed by benchmark")]
- [SuppressMessage("Design", "CA1051: field should be private", Justification = "needed by benchmark")]
- [Params(1, 2, 3)]
- public int Depth;
-
- [SuppressMessage("Design", "SA1401: field should be private", Justification = "needed by benchmark")]
- [SuppressMessage("Design", "CA1051: field should be private", Justification = "needed by benchmark")]
- [Params( 1, 10, 100, 1000)]
+ [Params(1, 10, 100, 1000)]
public int Changes;
-
- private TestClass _from;
- private TestClass _to;
- private IDisposable _binding;
- private Expression> _propertyExpression;
- [GlobalSetup(Targets = new[] { nameof(BindAndChangeUI), nameof(BindAndChangeOld), nameof(BindAndChangeNew), nameof(NoBind) })]
- public void CommonSetup()
+ [GlobalSetup(Targets = new[] { "BindAndChange_Depth1_UI", "BindAndChange_Depth1_Old", "BindAndChange_Depth1_New" })]
+ public void Depth1Setup()
+ {
+ _from = new TestClass(1);
+ _to = new TestClass(1);
+ }
+
+ [GlobalSetup(Targets = new[] { "BindAndChange_Depth2_UI", "BindAndChange_Depth2_Old", "BindAndChange_Depth2_New" })]
+ public void Depth2Setup()
{
- _from = new TestClass(Depth);
- _to = new TestClass(Depth);
- _propertyExpression = TestClass.GetValuePropertyExpression(Depth);
+ _from = new TestClass(2);
+ _to = new TestClass(2);
}
- //[BenchmarkCategory("No Binding")]
- //[Benchmark(Baseline = true)]
- public void NoBind()
+ [GlobalSetup(Targets = new[] { "BindAndChange_Depth3_UI", "BindAndChange_Depth3_Old", "BindAndChange_Depth3_New" })]
+ public void Depth3Setup()
{
- // We loop through the changes, alternating mutations to the source and destination
- // at every depth.
- var d2 = Depth * 2;
+ _from = new TestClass(3);
+ _to = new TestClass(3);
+ }
+
+ public void PerformMutations(int depth)
+ {
+ // We loop through the changes, alternating mutations to the source and destination at every depth.
+ var d2 = depth * 2;
for (var i = 0; i < Changes; ++i)
{
var a = i % d2;
@@ -68,80 +66,208 @@ public void NoBind()
}
}
- [BenchmarkCategory("Bind and Change")]
+ [BenchmarkCategory("Bind and Change Depth 1")]
+ [Benchmark(Baseline = true)]
+ public void BindAndChange_Depth1_UI()
+ {
+ using var binding = UI.Bind(_from, _to, x => x.Value, x => x.Value);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 1")]
+ [Benchmark]
+ public void BindAndChange_Depth1_Old()
+ {
+ using var binding = Old.Bind(_from, _to, x => x.Value, x => x.Value);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 1")]
+ [Benchmark]
+ public void BindAndChange_Depth1_New()
+ {
+ using var binding = New.Bind(_from, _to, x => x.Value, x => x.Value);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 2")]
+ [Benchmark(Baseline = true)]
+ public void BindAndChange_Depth2_UI()
+ {
+ using var binding = UI.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 2")]
+ [Benchmark]
+ public void BindAndChange_Depth2_Old()
+ {
+ using var binding = Old.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 2")]
+ [Benchmark]
+ public void BindAndChange_Depth2_New()
+ {
+ using var binding = New.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 3")]
[Benchmark(Baseline = true)]
- public void BindAndChangeUI()
+ public void BindAndChange_Depth3_UI()
+ {
+ using var binding = UI.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
+ PerformMutations(3);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 3")]
+ [Benchmark]
+ public void BindAndChange_Depth3_Old()
+ {
+ using var binding = Old.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
+ PerformMutations(3);
+ }
+
+ [BenchmarkCategory("Bind and Change Depth 3")]
+ [Benchmark]
+ public void BindAndChange_Depth3_New()
+ {
+ using var binding = New.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
+ PerformMutations(3);
+ }
+
+ [GlobalSetup(Target = "Change_Depth1_UI")]
+ public void Change_Depth1_UISetup()
+ {
+ Depth1Setup();
+ _binding = UI.Bind(_from, _to, x => x.Value, x => x.Value);
+ }
+
+ [BenchmarkCategory("Change Depth 1")]
+ [Benchmark(Baseline = true)]
+ public void Change_Depth1_UI()
+ {
+ PerformMutations(1);
+ }
+
+ [GlobalSetup(Target = "Change_Depth1_Old")]
+ public void Change_Depth1_OldSetup()
+ {
+ Depth1Setup();
+ _binding = Old.Bind(_from, _to, x => x.Value, x => x.Value);
+ }
+
+ [BenchmarkCategory("Change Depth 1")]
+ [Benchmark]
+ public void Change_Depth1_Old()
{
- using var binding = UI.Bind(_from, _to, _propertyExpression, _propertyExpression);
+ PerformMutations(1);
+ }
- NoBind();
+ [GlobalSetup(Target = "Change_Depth1_New")]
+ public void Change_Depth1_NewSetup()
+ {
+ Depth1Setup();
+ _binding = New.Bind(_from, _to, x => x.Value, x => x.Value);
}
- [BenchmarkCategory("Bind and Change")]
+ [BenchmarkCategory("Change Depth 1")]
[Benchmark]
- public void BindAndChangeOld()
+ public void Change_Depth1_New()
{
- using var binding = Old.Bind(_from, _to, _propertyExpression, _propertyExpression);
-
- NoBind();
+ PerformMutations(1);
}
- [BenchmarkCategory("Bind and Change")]
+ [GlobalSetup(Target = "Change_Depth2_UI")]
+ public void Change_Depth2_UISetup()
+ {
+ Depth2Setup();
+ _binding = UI.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ }
+
+ [BenchmarkCategory("Change Depth 2")]
+ [Benchmark(Baseline = true)]
+ public void Change_Depth2_UI()
+ {
+ PerformMutations(2);
+ }
+
+ [GlobalSetup(Target = "Change_Depth2_Old")]
+ public void Change_Depth2_OldSetup()
+ {
+ Depth2Setup();
+ _binding = Old.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ }
+
+ [BenchmarkCategory("Change Depth 2")]
[Benchmark]
- public void BindAndChangeNew()
+ public void Change_Depth2_Old()
+ {
+ PerformMutations(2);
+ }
+
+ [GlobalSetup(Target = "Change_Depth2_New")]
+ public void Change_Depth2_NewSetup()
{
- using var binding = New.Bind(_from, _to, _propertyExpression, _propertyExpression);
+ Depth2Setup();
+ _binding = New.Bind(_from, _to, x => x.Child.Value, x => x.Child.Value);
+ }
- NoBind();
+ [BenchmarkCategory("Change Depth 2")]
+ [Benchmark]
+ public void Change_Depth2_New()
+ {
+ PerformMutations(2);
}
- [GlobalSetup(Target = nameof(ChangeUI))]
- public void ChangUISetup()
+ [GlobalSetup(Target = "Change_Depth3_UI")]
+ public void Change_Depth3_UISetup()
{
- CommonSetup();
- _binding = UI.Bind(_from, _to, _propertyExpression, _propertyExpression);
+ Depth3Setup();
+ _binding = UI.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
}
- [BenchmarkCategory("Change")]
+ [BenchmarkCategory("Change Depth 3")]
[Benchmark(Baseline = true)]
- public void ChangeUI()
+ public void Change_Depth3_UI()
{
- NoBind();
+ PerformMutations(3);
}
- [GlobalSetup(Target = nameof(ChangeOld))]
- public void ChangOldSetup()
+ [GlobalSetup(Target = "Change_Depth3_Old")]
+ public void Change_Depth3_OldSetup()
{
- CommonSetup();
- _binding = Old.Bind(_from, _to, _propertyExpression, _propertyExpression);
+ Depth3Setup();
+ _binding = Old.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
}
- [BenchmarkCategory("Change")]
+ [BenchmarkCategory("Change Depth 3")]
[Benchmark]
- public void ChangeOld()
+ public void Change_Depth3_Old()
{
- NoBind();
+ PerformMutations(3);
}
- [GlobalSetup(Target = nameof(ChangeNew))]
- public void ChangNewSetup()
+ [GlobalSetup(Target = "Change_Depth3_New")]
+ public void Change_Depth3_NewSetup()
{
- CommonSetup();
- _binding = New.Bind(_from, _to, _propertyExpression, _propertyExpression);
+ Depth3Setup();
+ _binding = New.Bind(_from, _to, x => x.Child.Child.Value, x => x.Child.Child.Value);
}
- [BenchmarkCategory("Change")]
+ [BenchmarkCategory("Change Depth 3")]
[Benchmark]
- public void ChangeNew()
+ public void Change_Depth3_New()
{
- NoBind();
+ PerformMutations(3);
}
- [GlobalCleanup(Targets = new[] { nameof(ChangeUI), nameof(ChangeOld), nameof(ChangeNew) })]
+ [GlobalCleanup(Targets = new[] { "Change_Depth1_UI", "Change_Depth1_Old", "Change_Depth1_New", "Change_Depth2_UI", "Change_Depth2_Old", "Change_Depth2_New", "Change_Depth3_UI", "Change_Depth3_Old", "Change_Depth3_New" })]
public void GlobalCleanup()
{
- // Disposing logic
- _binding!.Dispose();
+ _binding.Dispose();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.tt b/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.tt
new file mode 100644
index 00000000..946fe841
--- /dev/null
+++ b/src/ReactiveMarbles.PropertyChanged.Benchmarks/BindBenchmarks.tt
@@ -0,0 +1,133 @@
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core.dll" #>
+<#@ assembly name="System.Collections.dll" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Linq" #>
+<#@ output extension=".cs" #>
+// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
+
+using ReactiveMarbles.PropertyChanged.Benchmarks.Moqs;
+<#
+// First entry will be classified as the baseline.
+var participants = new (string Alias, string MethodName, string FullClassName)[]
+{
+ ("UI", "Bind", "ReactiveUI.PropertyBindingMixins"),
+ ("Old", "Bind", "ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.BindExtensions"),
+ ("New", "Bind", "ReactiveMarbles.PropertyChanged.BindExtensions"),
+};
+var depths = new[] { 1, 2, 3 };
+const string BindAndChange = "BindAndChange";
+const string Change = "Change";
+string GetBenchmarkName(string baseName, int depth, string alias) => $"{baseName}_Depth{depth}_{alias}";
+
+foreach(var (Alias, MethodName, FullClassName) in participants)
+{
+#>
+using <#= Alias #> = <#= FullClassName #>;
+<#}#>
+
+namespace ReactiveMarbles.PropertyChanged.Benchmarks
+{
+ ///
+ /// Benchmarks for binding.
+ ///
+ [SimpleJob(RuntimeMoniker.NetCoreApp31)]
+ [MemoryDiagnoser]
+ [MarkdownExporterAttribute.GitHub]
+ [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+ public class BindBenchmarks
+ {
+ private TestClass _from;
+ private TestClass _to;
+ private IDisposable _binding;
+
+ ///
+ /// The number mutations to perform.
+ ///
+ [Params(1, 10, 100, 1000)]
+ public int Changes;
+
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i]; #>
+ [GlobalSetup(Targets = new[] { <#= string.Join(", ", participants.Select(x => $"\"{GetBenchmarkName(BindAndChange, depth, x.Alias)}\"")) #> })]
+ public void Depth<#= depth #>Setup()
+ {
+ _from = new TestClass(<#= depth #>);
+ _to = new TestClass(<#= depth #>);
+ }
+
+<# } #>
+ public void PerformMutations(int depth)
+ {
+ // We loop through the changes, alternating mutations to the source and destination at every depth.
+ var d2 = depth * 2;
+ for (var i = 0; i < Changes; ++i)
+ {
+ var a = i % d2;
+ var t = (a % 2) > 0 ? _to : _from;
+ t.Mutate(a / 2);
+ }
+ }
+
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i];
+ for (int j = 0; j < participants.Length; j++)
+ {
+ var (Alias, MethodName, FullClassName) = participants[j]; #>
+ [BenchmarkCategory("Bind and Change Depth <#= depth #>")]
+ <# if (j == 0) WriteLine("[Benchmark(Baseline = true)]"); else WriteLine("[Benchmark]"); #>
+ public void <#= GetBenchmarkName(BindAndChange, depth, Alias) #>()
+ {
+<#
+ var expression = string.Join(".", Enumerable.Range(1, depth - 1).Select(x => "Child").Prepend("x").Append("Value")); #>
+ using var binding = <#= $"{Alias}.{MethodName}(_from, _to, x => {expression}, x => {expression});" #>
+ PerformMutations(<#= depth #>);
+ }
+
+<# } #>
+<# } #>
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i];
+ for (int j = 0; j < participants.Length; j++)
+ {
+ var (Alias, MethodName, FullClassName) = participants[j];
+ var benchmarkName = GetBenchmarkName(Change, depth, Alias); #>
+ [GlobalSetup(Target = <#= $"\"{benchmarkName}\"" #>)]
+ public void <#= $"{benchmarkName}Setup" #>()
+ {
+<#
+ var expression = string.Join(".", Enumerable.Range(1, depth - 1).Select(x => "Child").Prepend("x").Append("Value")); #>
+ Depth<#= depth #>Setup();
+ _binding = <#= $"{Alias}.{MethodName}(_from, _to, x => {expression}, x => {expression});" #>
+ }
+
+ [BenchmarkCategory("Change Depth <#= depth #>")]
+ <# if (j == 0) WriteLine("[Benchmark(Baseline = true)]"); else WriteLine("[Benchmark]"); #>
+ public void <#= benchmarkName #>()
+ {
+ PerformMutations(<#= depth #>);
+ }
+
+<# } #>
+<#
+ } #>
+ [GlobalCleanup(Targets = new[] { <#= string.Join(", ", depths.SelectMany(depth => participants.Select(x => $"\"{GetBenchmarkName(Change, depth, x.Alias)}\""))) #> })]
+ public void GlobalCleanup()
+ {
+ _binding.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/PropertyChangesBenchmarks.cs b/src/ReactiveMarbles.PropertyChanged.Benchmarks/PropertyChangesBenchmarks.cs
deleted file mode 100644
index 7da45c09..00000000
--- a/src/ReactiveMarbles.PropertyChanged.Benchmarks/PropertyChangesBenchmarks.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
-// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for full license information.
-
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq.Expressions;
-using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Jobs;
-
-using ReactiveMarbles.PropertyChanged.Benchmarks.Moqs;
-using ReactiveUI;
-using New = ReactiveMarbles.PropertyChanged.NotifyPropertyChangedExtensions;
-using Old = ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.NotifyPropertyChangedExtensions;
-using UI = ReactiveUI.WhenAnyMixin;
-
-namespace ReactiveMarbles.PropertyChanged.Benchmarks
-{
- ///
- /// Benchmarks for the property changed.
- ///
- [SimpleJob(RuntimeMoniker.NetCoreApp31)]
- [MemoryDiagnoser]
- [MarkdownExporterAttribute.GitHub]
- [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
- public class PropertyChangesBenchmarks
- {
- ///
- /// The parameters for the tests.
- ///
- [SuppressMessage("Design", "SA1401: field should be private", Justification = "needed by benchmark")]
- [SuppressMessage("Design", "CA1051: field should be private", Justification = "needed by benchmark")]
- [Params(1, 2, 3)]
- public int Depth;
-
- ///
- /// The parameters for the tests.
- ///
- [SuppressMessage("Design", "SA1401: field should be private", Justification = "needed by benchmark")]
- [SuppressMessage("Design", "CA1051: field should be private", Justification = "needed by benchmark")]
- [Params(1, 10, 100, 1000)]
- public int Changes;
-
- private TestClass _from;
- private int _to;
- private IDisposable _subscription;
-
- private Expression> _propertyExpression;
-
- [GlobalSetup(Targets = new[] { nameof(SubscribeAndChangeUI), nameof(SubscribeAndChangeOld), nameof(SubscribeAndChangeNew), nameof(NoBind) })]
- public void CommonSetup()
- {
- _from = new TestClass(Depth);
- _propertyExpression = TestClass.GetValuePropertyExpression(Depth);
- }
-
- //[BenchmarkCategory("No Binding")]
- //[Benchmark(Baseline = true)]
- public void NoBind()
- {
- // We loop through the changes, creating mutations at every depth.
- for (var i = 0; i < Changes; ++i)
- {
- _from.Mutate(i % Depth);
- }
- }
-
- [BenchmarkCategory("Subscribe and Change")]
- [Benchmark(Baseline = true)]
- public void SubscribeAndChangeUI()
- {
- using var subscription = UI.WhenAnyValue(_from, _propertyExpression).Subscribe(x => _to = x);
-
- NoBind();
- }
-
- [BenchmarkCategory("Subscribe and Change")]
- [Benchmark]
- public void SubscribeAndChangeOld()
- {
- using var subscription = Old.WhenPropertyValueChanges(_from, _propertyExpression).Subscribe(x => _to = x);
-
- NoBind();
- }
-
- [BenchmarkCategory("Subscribe and Change")]
- [Benchmark]
- public void SubscribeAndChangeNew()
- {
- using var subscription = New.WhenPropertyValueChanges(_from, _propertyExpression).Subscribe(x => _to = x);
-
- NoBind();
- }
-
- [GlobalSetup(Target = nameof(ChangeUI))]
- public void ChangUISetup()
- {
- CommonSetup();
- _subscription = UI.WhenAnyValue(_from, _propertyExpression).Subscribe(x => _to = x);
- }
-
- [BenchmarkCategory("Change")]
- [Benchmark(Baseline = true)]
- public void ChangeUI()
- {
- NoBind();
- }
-
- [GlobalSetup(Target = nameof(ChangeOld))]
- public void ChangOldSetup()
- {
- CommonSetup();
- _subscription = Old.WhenPropertyValueChanges(_from, _propertyExpression).Subscribe(x => _to = x);
- }
-
- [BenchmarkCategory("Change")]
- [Benchmark]
- public void ChangeOld()
- {
- NoBind();
- }
-
- [GlobalSetup(Target = nameof(ChangeNew))]
- public void ChangNewSetup()
- {
- CommonSetup();
- _subscription = New.WhenPropertyValueChanges(_from, _propertyExpression).Subscribe(x => _to = x);
- }
-
- [BenchmarkCategory("Change")]
- [Benchmark]
- public void ChangeNew()
- {
- NoBind();
- }
-
- [GlobalCleanup(Targets = new[] { nameof(ChangeUI), nameof(ChangeOld), nameof(ChangeNew) })]
- public void GlobalCleanup()
- {
- // Disposing logic
- _subscription!.Dispose();
- }
- }
-}
\ No newline at end of file
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/ReactiveMarbles.PropertyChanged.Benchmarks.csproj b/src/ReactiveMarbles.PropertyChanged.Benchmarks/ReactiveMarbles.PropertyChanged.Benchmarks.csproj
index 0d06c8d1..26b34f33 100644
--- a/src/ReactiveMarbles.PropertyChanged.Benchmarks/ReactiveMarbles.PropertyChanged.Benchmarks.csproj
+++ b/src/ReactiveMarbles.PropertyChanged.Benchmarks/ReactiveMarbles.PropertyChanged.Benchmarks.csproj
@@ -1,4 +1,4 @@
-
+
Exe
netcoreapp3.1
@@ -13,4 +13,33 @@
+
+
+
+ TextTemplatingFileGenerator
+ BindBenchmarks.cs
+
+
+ TextTemplatingFileGenerator
+ WhenChangedBenchmarks.cs
+
+
+
+
+
+ True
+ True
+ BindBenchmarks.tt
+
+
+ True
+ True
+ WhenChangedBenchmarks.tt
+
+
+
+
+
+
+
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.cs b/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.cs
new file mode 100644
index 00000000..d6ef174b
--- /dev/null
+++ b/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.cs
@@ -0,0 +1,267 @@
+// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
+
+using ReactiveMarbles.PropertyChanged.Benchmarks.Moqs;
+using UI = ReactiveUI.WhenAnyMixin;
+using Old = ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.NotifyPropertyChangedExtensions;
+using New = ReactiveMarbles.PropertyChanged.NotifyPropertyChangedExtensions;
+
+namespace ReactiveMarbles.PropertyChanged.Benchmarks
+{
+ ///
+ /// Benchmarks for the property changed.
+ ///
+ [SimpleJob(RuntimeMoniker.NetCoreApp31)]
+ [MemoryDiagnoser]
+ [MarkdownExporterAttribute.GitHub]
+ [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+ public class WhenChangedBenchmarks
+ {
+ private TestClass _from;
+ private int _to;
+ private IDisposable _subscription;
+
+ ///
+ /// The number mutations to perform.
+ ///
+ [Params(1, 10, 100, 1000)]
+ public int Changes;
+
+ [GlobalSetup(Targets = new[] { "SubscribeAndChange_Depth1_UI", "SubscribeAndChange_Depth1_Old", "SubscribeAndChange_Depth1_New" })]
+ public void Depth1Setup()
+ {
+ _from = new TestClass(1);
+ }
+
+ [GlobalSetup(Targets = new[] { "SubscribeAndChange_Depth2_UI", "SubscribeAndChange_Depth2_Old", "SubscribeAndChange_Depth2_New" })]
+ public void Depth2Setup()
+ {
+ _from = new TestClass(2);
+ }
+
+ [GlobalSetup(Targets = new[] { "SubscribeAndChange_Depth3_UI", "SubscribeAndChange_Depth3_Old", "SubscribeAndChange_Depth3_New" })]
+ public void Depth3Setup()
+ {
+ _from = new TestClass(3);
+ }
+
+ public void PerformMutations(int depth)
+ {
+ // We loop through the changes, creating mutations at every depth.
+ for (var i = 0; i < Changes; ++i)
+ {
+ _from.Mutate(i % depth);
+ }
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 1")]
+ [Benchmark(Baseline = true)]
+ public void SubscribeAndChange_Depth1_UI()
+ {
+ using var subscription = UI.WhenAnyValue(_from, x => x.Value).Subscribe(x => _to = x);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 1")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth1_Old()
+ {
+ using var subscription = Old.WhenPropertyValueChanges(_from, x => x.Value).Subscribe(x => _to = x);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 1")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth1_New()
+ {
+ using var subscription = New.WhenPropertyValueChanges(_from, x => x.Value).Subscribe(x => _to = x);
+ PerformMutations(1);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 2")]
+ [Benchmark(Baseline = true)]
+ public void SubscribeAndChange_Depth2_UI()
+ {
+ using var subscription = UI.WhenAnyValue(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 2")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth2_Old()
+ {
+ using var subscription = Old.WhenPropertyValueChanges(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 2")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth2_New()
+ {
+ using var subscription = New.WhenPropertyValueChanges(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(2);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 3")]
+ [Benchmark(Baseline = true)]
+ public void SubscribeAndChange_Depth3_UI()
+ {
+ using var subscription = UI.WhenAnyValue(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(3);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 3")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth3_Old()
+ {
+ using var subscription = Old.WhenPropertyValueChanges(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(3);
+ }
+
+ [BenchmarkCategory("Subscribe and Change Depth 3")]
+ [Benchmark]
+ public void SubscribeAndChange_Depth3_New()
+ {
+ using var subscription = New.WhenPropertyValueChanges(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ PerformMutations(3);
+ }
+
+ [GlobalSetup(Target = "Change_Depth1_UI")]
+ public void Change_Depth1_UISetup()
+ {
+ Depth1Setup();
+ _subscription = UI.WhenAnyValue(_from, x => x.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 1")]
+ [Benchmark(Baseline = true)]
+ public void Change_Depth1_UI()
+ {
+ PerformMutations(1);
+ }
+
+ [GlobalSetup(Target = "Change_Depth1_Old")]
+ public void Change_Depth1_OldSetup()
+ {
+ Depth1Setup();
+ _subscription = Old.WhenPropertyValueChanges(_from, x => x.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 1")]
+ [Benchmark]
+ public void Change_Depth1_Old()
+ {
+ PerformMutations(1);
+ }
+
+ [GlobalSetup(Target = "Change_Depth1_New")]
+ public void Change_Depth1_NewSetup()
+ {
+ Depth1Setup();
+ _subscription = New.WhenPropertyValueChanges(_from, x => x.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 1")]
+ [Benchmark]
+ public void Change_Depth1_New()
+ {
+ PerformMutations(1);
+ }
+
+ [GlobalSetup(Target = "Change_Depth2_UI")]
+ public void Change_Depth2_UISetup()
+ {
+ Depth2Setup();
+ _subscription = UI.WhenAnyValue(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 2")]
+ [Benchmark(Baseline = true)]
+ public void Change_Depth2_UI()
+ {
+ PerformMutations(2);
+ }
+
+ [GlobalSetup(Target = "Change_Depth2_Old")]
+ public void Change_Depth2_OldSetup()
+ {
+ Depth2Setup();
+ _subscription = Old.WhenPropertyValueChanges(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 2")]
+ [Benchmark]
+ public void Change_Depth2_Old()
+ {
+ PerformMutations(2);
+ }
+
+ [GlobalSetup(Target = "Change_Depth2_New")]
+ public void Change_Depth2_NewSetup()
+ {
+ Depth2Setup();
+ _subscription = New.WhenPropertyValueChanges(_from, x => x.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 2")]
+ [Benchmark]
+ public void Change_Depth2_New()
+ {
+ PerformMutations(2);
+ }
+
+ [GlobalSetup(Target = "Change_Depth3_UI")]
+ public void Change_Depth3_UISetup()
+ {
+ Depth3Setup();
+ _subscription = UI.WhenAnyValue(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 3")]
+ [Benchmark(Baseline = true)]
+ public void Change_Depth3_UI()
+ {
+ PerformMutations(3);
+ }
+
+ [GlobalSetup(Target = "Change_Depth3_Old")]
+ public void Change_Depth3_OldSetup()
+ {
+ Depth3Setup();
+ _subscription = Old.WhenPropertyValueChanges(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 3")]
+ [Benchmark]
+ public void Change_Depth3_Old()
+ {
+ PerformMutations(3);
+ }
+
+ [GlobalSetup(Target = "Change_Depth3_New")]
+ public void Change_Depth3_NewSetup()
+ {
+ Depth3Setup();
+ _subscription = New.WhenPropertyValueChanges(_from, x => x.Child.Child.Value).Subscribe(x => _to = x);
+ }
+
+ [BenchmarkCategory("Change Depth 3")]
+ [Benchmark]
+ public void Change_Depth3_New()
+ {
+ PerformMutations(3);
+ }
+
+ [GlobalCleanup(Targets = new[] { "Change_Depth1_UI", "Change_Depth1_Old", "Change_Depth1_New", "Change_Depth2_UI", "Change_Depth2_Old", "Change_Depth2_New", "Change_Depth3_UI", "Change_Depth3_Old", "Change_Depth3_New" })]
+ public void GlobalCleanup()
+ {
+ _subscription.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.tt b/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.tt
new file mode 100644
index 00000000..5db04ac0
--- /dev/null
+++ b/src/ReactiveMarbles.PropertyChanged.Benchmarks/WhenChangedBenchmarks.tt
@@ -0,0 +1,129 @@
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core.dll" #>
+<#@ assembly name="System.Collections.dll" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Linq" #>
+<#@ output extension=".cs" #>
+// Copyright (c) 2019-2020 ReactiveUI Association Incorporated. All rights reserved.
+// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
+
+using ReactiveMarbles.PropertyChanged.Benchmarks.Moqs;
+<#
+// First entry will be classified as the baseline.
+var participants = new (string Alias, string MethodName, string FullClassName)[]
+{
+ ("UI", "WhenAnyValue", "ReactiveUI.WhenAnyMixin"),
+ ("Old", "WhenPropertyValueChanges", "ReactiveMarbles.PropertyChanged.Benchmarks.Legacy.NotifyPropertyChangedExtensions"),
+ ("New", "WhenPropertyValueChanges", "ReactiveMarbles.PropertyChanged.NotifyPropertyChangedExtensions"),
+};
+var depths = new[] { 1, 2, 3 };
+const string SubscribeAndChange = "SubscribeAndChange";
+const string Change = "Change";
+string GetBenchmarkName(string baseName, int depth, string alias) => $"{baseName}_Depth{depth}_{alias}";
+
+foreach(var (Alias, MethodName, FullClassName) in participants)
+{
+#>
+using <#= Alias #> = <#= FullClassName #>;
+<#}#>
+
+namespace ReactiveMarbles.PropertyChanged.Benchmarks
+{
+ ///
+ /// Benchmarks for the property changed.
+ ///
+ [SimpleJob(RuntimeMoniker.NetCoreApp31)]
+ [MemoryDiagnoser]
+ [MarkdownExporterAttribute.GitHub]
+ [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+ public class WhenChangedBenchmarks
+ {
+ private TestClass _from;
+ private int _to;
+ private IDisposable _subscription;
+
+ ///
+ /// The number mutations to perform.
+ ///
+ [Params(1, 10, 100, 1000)]
+ public int Changes;
+
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i]; #>
+ [GlobalSetup(Targets = new[] { <#= string.Join(", ", participants.Select(x => $"\"{GetBenchmarkName(SubscribeAndChange, depth, x.Alias)}\"")) #> })]
+ public void Depth<#= depth #>Setup()
+ {
+ _from = new TestClass(<#= depth #>);
+ }
+
+<# } #>
+ public void PerformMutations(int depth)
+ {
+ // We loop through the changes, creating mutations at every depth.
+ for (var i = 0; i < Changes; ++i)
+ {
+ _from.Mutate(i % depth);
+ }
+ }
+
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i];
+ for (int j = 0; j < participants.Length; j++)
+ {
+ var (Alias, MethodName, FullClassName) = participants[j]; #>
+ [BenchmarkCategory("Subscribe and Change Depth <#= depth #>")]
+ <# if (j == 0) WriteLine("[Benchmark(Baseline = true)]"); else WriteLine("[Benchmark]"); #>
+ public void <#= GetBenchmarkName(SubscribeAndChange, depth, Alias) #>()
+ {
+<#
+ var expression = string.Join(".", Enumerable.Range(1, depth - 1).Select(x => "Child").Prepend("x").Append("Value")); #>
+ using var subscription = <#= $"{Alias}.{MethodName}(_from, x => {expression}).Subscribe(x => _to = x);" #>
+ PerformMutations(<#= depth #>);
+ }
+
+<# } #>
+<# } #>
+<#
+ for(int i = 0; i < depths.Length; i++)
+ {
+ int depth = depths[i];
+ for (int j = 0; j < participants.Length; j++)
+ {
+ var (Alias, MethodName, FullClassName) = participants[j];
+ var benchmarkName = GetBenchmarkName(Change, depth, Alias); #>
+ [GlobalSetup(Target = <#= $"\"{benchmarkName}\"" #>)]
+ public void <#= $"{benchmarkName}Setup" #>()
+ {
+<#
+ var expression = string.Join(".", Enumerable.Range(1, depth - 1).Select(x => "Child").Prepend("x").Append("Value")); #>
+ Depth<#= depth #>Setup();
+ _subscription = <#= $"{Alias}.{MethodName}(_from, x => {expression}).Subscribe(x => _to = x);" #>
+ }
+
+ [BenchmarkCategory("Change Depth <#= depth #>")]
+ <# if (j == 0) WriteLine("[Benchmark(Baseline = true)]"); else WriteLine("[Benchmark]"); #>
+ public void <#= benchmarkName #>()
+ {
+ PerformMutations(<#= depth #>);
+ }
+
+<# } #>
+<#
+ } #>
+ [GlobalCleanup(Targets = new[] { <#= string.Join(", ", depths.SelectMany(depth => participants.Select(x => $"\"{GetBenchmarkName(Change, depth, x.Alias)}\""))) #> })]
+ public void GlobalCleanup()
+ {
+ _subscription.Dispose();
+ }
+ }
+}
\ No newline at end of file