Skip to content

Commit

Permalink
Merge pull request #97 from nventive/dev/jpl/benchmarks
Browse files Browse the repository at this point in the history
benchmarks: Improve benchmark for better precision and run time.
  • Loading branch information
jeanplevesque authored Jan 10, 2024
2 parents 8bc6744 + 360435f commit 80a56ae
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 126 deletions.
2 changes: 2 additions & 0 deletions .github/.commitsar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
commits:
strict: false
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ GitHub Issue: #
- [ ] **None** (The library is unchanged.)
- Only code under the `build` folder was changed.
- Only code under the `.github` folder was changed.
- Only code in the Benchmarks project was changed.

## Checklist

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/conventional-commits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ jobs:
uses: actions/checkout@v1
- name: Commitsar check
uses: docker://aevea/commitsar
env:
COMMITSAR_CONFIG_PATH : ./.github
2 changes: 1 addition & 1 deletion build/gitversion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ commit-message-incrementing: Enabled
major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)"
minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:"
patch-version-bump-message: "^(build|chore|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:"
no-bump-message: "^(ci)(\\([\\w\\s-]*\\))?:" # You can use the "ci" type to avoid bumping the version when your changes are limited to the build or .github folders.
no-bump-message: "^(ci|benchmarks)(\\([\\w\\s-]*\\))?:" # You can use the "ci" or "benchmarks" type to avoid bumping the version when your changes are limited to the [build or .github folders] or limited to benchmark code.
branches:
main:
regex: ^master$|^main$
Expand Down
111 changes: 0 additions & 111 deletions src/DynamicMvvm.Benchmarks/Benchmark.cs

This file was deleted.

1 change: 1 addition & 0 deletions src/DynamicMvvm.Benchmarks/DynamicMvvm.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
234 changes: 234 additions & 0 deletions src/DynamicMvvm.Benchmarks/IViewModel.Extensions.Benchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Chinook.DynamicMvvm;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace DynamicMvvm.Benchmarks
{
[MemoryDiagnoser]
[MaxIterationCount(36)]
[MaxWarmupCount(16)]
public class ViewModelExtensionsBenchmark
{
internal static readonly IServiceProvider ServiceProvider = new HostBuilder()
.ConfigureServices(serviceCollection => serviceCollection
.AddSingleton<IDynamicCommandBuilderFactory, DynamicCommandBuilderFactory>()
.AddSingleton<IDynamicPropertyFactory, DynamicPropertyFactory>()
)
.Build()
.Services;

internal static readonly Type DynamicViewModelType = GetDynamicViewModelType();
private const int PropertyCount = 32;
private const int ViewModelCount = 1024 * 1024;
private static string[] propertyNames = Enumerable.Range(0, PropertyCount).Select(i => $"Number{i}").ToArray();

/// <summary>
/// Generates a dynamic type that inherits from <see cref="ViewModelBase"/> and has <paramref name="propertyCount"/> properties.
/// </summary>
/// <param name="propertyCount">The amount of property defined in the dynamic class.</param>
/// <returns>A dynamically generated type.</returns>
internal static Type GetDynamicViewModelType(int propertyCount = PropertyCount)
{
// Create a new dynamic assembly
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

// Create a new module within the assembly
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");

// Create a new type that inherits from ViewModelBase
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicClass", TypeAttributes.Public | TypeAttributes.Class, typeof(ViewModelBase));

// Define a parameterless constructor
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(string), typeof(IServiceProvider) });
ILGenerator constructorIL = constructorBuilder.GetILGenerator();
constructorIL.Emit(OpCodes.Ldarg_0);
constructorIL.Emit(OpCodes.Ldarg_1);
constructorIL.Emit(OpCodes.Ldarg_2);
constructorIL.Emit(OpCodes.Call, typeof(ViewModelBase).GetConstructor(new Type[] { typeof(string), typeof(IServiceProvider) })!);
constructorIL.Emit(OpCodes.Ret);

// There is a name conflict on IViewModelExtensions which requires us to use reflection to get it (because typeof(IViewModelExtensions) is ambiguous).
var viewModelExtensionsType = Assembly.GetAssembly(typeof(IViewModel))!.GetType("Chinook.DynamicMvvm.IViewModelExtensions")!;

for (int i = 0; i < propertyCount; i++)
{
// Define a 'NumberX' property with the specified getter and setter
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty("Number" + i, PropertyAttributes.None, typeof(int), null);

MethodBuilder getMethod = typeBuilder.DefineMethod("get_Number" + i, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(int), Type.EmptyTypes);
ILGenerator getMethodIL = getMethod.GetILGenerator();
getMethodIL.Emit(OpCodes.Ldarg_0);
getMethodIL.Emit(OpCodes.Ldc_I4_S, 42); // Initial value
getMethodIL.Emit(OpCodes.Ldstr, "Number" + i);
var getMethodInfo = viewModelExtensionsType.GetMethods()
.Where(m => m.Name == "Get" && m.IsGenericMethod)
.First(m =>
{
var parameters = m.GetParameters();
return parameters.Length == 3 && parameters[0].ParameterType == typeof(IViewModel) && parameters[2].ParameterType == typeof(string);
});
getMethodIL.EmitCall(OpCodes.Call, getMethodInfo.MakeGenericMethod(typeof(int)), null);
getMethodIL.Emit(OpCodes.Ret);

MethodBuilder setMethod = typeBuilder.DefineMethod("set_Number" + i, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new Type[] { typeof(int) });
ILGenerator setMethodIL = setMethod.GetILGenerator();
setMethodIL.Emit(OpCodes.Ldarg_0);
setMethodIL.Emit(OpCodes.Ldarg_1);
setMethodIL.Emit(OpCodes.Ldstr, "Number" + i);
var setMethodInfo = viewModelExtensionsType.GetMethods()
.Where(m => m.Name == "Set" && m.IsGenericMethod)
.First(m =>
{
var parameters = m.GetParameters();
return parameters.Length == 3 && parameters[0].ParameterType == typeof(IViewModel) && parameters[2].ParameterType == typeof(string);
});
setMethodIL.EmitCall(OpCodes.Call, setMethodInfo.MakeGenericMethod(typeof(int)), null);
setMethodIL.Emit(OpCodes.Ret);

propertyBuilder.SetGetMethod(getMethod);
propertyBuilder.SetSetMethod(setMethod);
}

// Create the type
Type dynamicType = typeBuilder.CreateType();

return dynamicType;
}

/// <summary>
/// This vm always initializes new property instances when being invoked.
/// </summary>
private NeverInitiatedViewModel _neverInitiatedVM = new();

private InitiatedViewModel _initiatedVM = new();

private IViewModel[]? _vmsForPropertySetter;
private int _i = 0;

[GlobalSetup(Target = nameof(Set_Unresolved))]
public void SetupVMForPropertySetter()
{
_i = 0;
_vmsForPropertySetter = new IViewModel[ViewModelCount];
for (int i = 0; i < ViewModelCount; i++)
{
_vmsForPropertySetter[i] = (IViewModel)Activator.CreateInstance(DynamicViewModelType, "ViewModelName", ServiceProvider)!;
}
}

[Benchmark]
public int GetFromValue_Unresolved()
{
return _neverInitiatedVM.Number;
}

[Benchmark]
public int GetFromObservable_Unresolved()
{
return _neverInitiatedVM.ObservableNumber;
}

[Benchmark]
public int GetFromValue_Resolved()
{
return _initiatedVM.Number;
}

[Benchmark]
public int GetFromObservable_Resolved()
{
return _initiatedVM.ObservableNumber;
}

[Benchmark(OperationsPerInvoke = PropertyCount)]
[MaxIterationCount(24)]
public void Set_Unresolved()
{
var i = Interlocked.Increment(ref _i);
var vm = _vmsForPropertySetter![i];
for (int propertyIndex = 0; propertyIndex < PropertyCount; propertyIndex++)
{
vm!.Set(i, propertyNames[propertyIndex]);
}
}

[Benchmark]
public void Set_Resolved()
{
_initiatedVM.Number = 1;
}
}

public sealed class NeverInitiatedViewModel : TestViewModelBase
{
public NeverInitiatedViewModel(IServiceProvider? serviceProvider = null)
: base(serviceProvider ?? ViewModelExtensionsBenchmark.ServiceProvider)
{
}

public int Number
{
get => this.Get<int>(initialValue: 42);
set => this.Set(value);
}

public int ObservableNumber
{
get => this.GetFromObservable<int>(Observable.Never<int>(), initialValue: 0);
set => this.Set(value);
}
}

public sealed class InitiatedViewModel : TestViewModelWithProperty
{
public InitiatedViewModel()
{
ServiceProvider = ViewModelExtensionsBenchmark.ServiceProvider;

Resolve(Number);
Resolve(ObservableNumber);
}

public int Number
{
get => this.Get<int>(initialValue: 42);
set => this.Set(value);
}

public int ObservableNumber
{
get => this.GetFromObservable<int>(Observable.Never<int>(), initialValue: 0);
set => this.Set(value);
}
}

public class TestViewModelWithProperty : TestViewModelBase
{
IDictionary<string, IDisposable> _disposables = new Dictionary<string, IDisposable>();

protected void Resolve(object value)
{
}

public override void AddDisposable(string key, IDisposable disposable)
{
_disposables[key] = disposable;
}

public override bool TryGetDisposable(string key, out IDisposable? disposable)
{
return _disposables.TryGetValue(key, out disposable);
}
}
}
Loading

0 comments on commit 80a56ae

Please sign in to comment.