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