Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added [ParamsAllValues] #664

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/guide/Advanced/Params.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,49 @@ public class IntroIParam
| Benchmark | (100,20) | 120.4 ms | 0.0843 ms | 0.0788 ms |
| Benchmark | (200,10) | 210.4 ms | 0.0892 ms | 0.0834 ms |
| Benchmark | (200,20) | 220.4 ms | 0.0949 ms | 0.0887 ms |

# ParamsAllValues

If you want to use all possible values of an `enum` or another type with a small number of values, you can use `ParamsAllValues`, instead of listing all the values by hand. The types supported by this attribute are:

* `bool`
* any `enum` that is not marked with `[Flags]`
* `Nullable<T>`, where `T` is a supported type

## Example (ParamsAllValues)

```c#
public class IntroParamsAllValues
{
public enum CustomEnum
{
A,
BB,
CCC
}

[ParamsAllValues]
public CustomEnum E { get; set; }

[ParamsAllValues]
public bool? B { get; set; }

[Benchmark]
public void Benchmark()
{
Thread.Sleep(E.ToString().Length * 100 + (B == true ? 20 : B == false ? 10 : 0));
}
}
```

| Method | E | B | Mean | Error | StdDev |
|---------- |---- |------ |---------:|----------:|----------:|
| Benchmark | A | ? | 100.3 ms | 0.0364 ms | 0.0341 ms |
| Benchmark | A | False | 110.3 ms | 0.0563 ms | 0.0527 ms |
| Benchmark | A | True | 120.3 ms | 0.0448 ms | 0.0419 ms |
| Benchmark | BB | ? | 200.3 ms | 0.0386 ms | 0.0342 ms |
| Benchmark | BB | False | 210.3 ms | 0.0554 ms | 0.0518 ms |
| Benchmark | BB | True | 220.3 ms | 0.0517 ms | 0.0458 ms |
| Benchmark | CCC | ? | 300.3 ms | 0.0213 ms | 0.0178 ms |
| Benchmark | CCC | False | 310.3 ms | 0.0457 ms | 0.0428 ms |
| Benchmark | CCC | True | 320.3 ms | 0.0390 ms | 0.0345 ms |
27 changes: 27 additions & 0 deletions samples/BenchmarkDotNet.Samples/Intro/IntroParamsAllValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Threading;
using BenchmarkDotNet.Attributes;

namespace BenchmarkDotNet.Samples.Intro
{
public class IntroParamsAllValues
{
public enum CustomEnum
{
A,
BB,
CCC
}

[ParamsAllValues]
public CustomEnum E { get; set; }

[ParamsAllValues]
public bool? B { get; set; }

[Benchmark]
public void Benchmark()
{
Thread.Sleep(E.ToString().Length * 100 + (B == true ? 20 : B == false ? 10 : 0));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace BenchmarkDotNet.Attributes
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ParamsAllValuesAttribute : Attribute
{
}
}
49 changes: 36 additions & 13 deletions src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,26 @@ private static Target CreateTarget(

private static ParameterDefinitions GetParameterDefinitions(Type type)
{
const BindingFlags reflectionFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

var allParamsMembers = type.GetTypeMembersWithGivenAttribute<ParamsAttribute>(reflectionFlags);
IEnumerable<ParameterDefinition> GetDefinitions<TAttribute>(Func<TAttribute, Type, object[]> getValidValues) where TAttribute : Attribute
{
const BindingFlags reflectionFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

var allParamsSourceMembers = type.GetTypeMembersWithGivenAttribute<ParamsSourceAttribute>(reflectionFlags);
var allMembers = type.GetTypeMembersWithGivenAttribute<TAttribute>(reflectionFlags);

var definitions = allParamsMembers
.Select(member =>
new ParameterDefinition(
member.Name,
member.IsStatic,
GetValidValues(member.Attribute.Values, member.ParameterType)))
.Concat(allParamsSourceMembers.Select(member =>
return allMembers.Select(member =>
new ParameterDefinition(
member.Name,
member.IsStatic,
GetValidValuesForParamsSource(type, member.Attribute.Name))))
.ToArray();
getValidValues(member.Attribute, member.ParameterType)));
}

var paramsDefinitions = GetDefinitions<ParamsAttribute>((attribute, parameterType) => GetValidValues(attribute.Values, parameterType));

var paramsSourceDefinitions = GetDefinitions<ParamsSourceAttribute>((attribute, _) => GetValidValuesForParamsSource(type, attribute.Name));

var paramsAllValuesDefinitions = GetDefinitions<ParamsAllValuesAttribute>((_, paramaterType) => GetAllValidValues(paramaterType));

var definitions = paramsDefinitions.Concat(paramsSourceDefinitions).Concat(paramsAllValuesDefinitions).ToArray();

return new ParameterDefinitions(definitions);
}
Expand Down Expand Up @@ -276,5 +278,26 @@ private static object[] ToArray(object sourceValue, MemberInfo memberInfo, Type

return values.ToArray();
}

private static object[] GetAllValidValues(Type parameterType)
{
if (parameterType == typeof(bool))
return new object[] { false, true };

if (parameterType.GetTypeInfo().IsEnum)
{
if (parameterType.GetTypeInfo().IsDefined(typeof(FlagsAttribute)))
throw new InvalidOperationException($"Unable to use {parameterType.Name} with [ParamsAllValues], because it's flags enum.");

return Enum.GetValues(parameterType).Cast<object>().ToArray();
}

var nullableUnderlyingType = Nullable.GetUnderlyingType(parameterType);
if (nullableUnderlyingType != null)
return new object[] { null }.Concat(GetAllValidValues(nullableUnderlyingType)).ToArray();

throw new InvalidOperationException(
$"Type {parameterType.Name} cannot be used with [ParamsAllValues], allowed types are: bool, enum types and nullable type for another allowed type.");
}
}
}
191 changes: 191 additions & 0 deletions tests/BenchmarkDotNet.IntegrationTests/ParamsAllValuesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Tests.Loggers;
using Xunit;
using Xunit.Abstractions;

namespace BenchmarkDotNet.IntegrationTests
{
public class ParamsAllValuesTestBoolTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestBoolTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
var logger = new OutputLogger(Output);
var config = CreateSimpleConfig(logger);

CanExecute<ParamsAllValuesTestBool>(config);
foreach (var param in new[] { false, true })
Assert.Contains($"// ### Parameter {param} ###" + Environment.NewLine, logger.GetLog());
}

public class ParamsAllValuesTestBool
{
[ParamsAllValues]
public bool ParamProperty { get; set; }

[Benchmark]
public void Benchmark() => Console.WriteLine($"// ### Parameter {ParamProperty} ###");
}
}

public class ParamsAllValuesTestEnumTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestEnumTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
var logger = new OutputLogger(Output);
var config = CreateSimpleConfig(logger);

CanExecute<ParamsAllValuesTestEnum>(config);
foreach (var param in new[] { TestEnum.A, TestEnum.B, TestEnum.C })
Assert.Contains($"// ### Parameter {param} ###" + Environment.NewLine, logger.GetLog());
Assert.DoesNotContain($"// ### Parameter {default(TestEnum)} ###" + Environment.NewLine, logger.GetLog());
}

public class ParamsAllValuesTestEnum
{
[ParamsAllValues]
public TestEnum ParamProperty { get; set; }

[Benchmark]
public void Benchmark() => Console.WriteLine($"// ### Parameter {ParamProperty} ###");
}

public enum TestEnum
{
A = 1, B, C
}
}

public class ParamsAllValuesTestNullableBoolTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestNullableBoolTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
var logger = new OutputLogger(Output);
var config = CreateSimpleConfig(logger);

CanExecute<ParamsAllValuesTestNullableBool>(config);
foreach (var param in new bool?[] { null, false, true })
Assert.Contains($"// ### Parameter {param} ###" + Environment.NewLine, logger.GetLog());
}

public class ParamsAllValuesTestNullableBool
{
[ParamsAllValues]
public bool? ParamProperty { get; set; }

[Benchmark]
public void Benchmark() => Console.WriteLine($"// ### Parameter {ParamProperty} ###");
}
}

public class ParamsAllValuesTestNullableEnumTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestNullableEnumTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
var logger = new OutputLogger(Output);
var config = CreateSimpleConfig(logger);

CanExecute<ParamsAllValuesTestNullableEnum>(config);
foreach (var param in new TestEnum?[] { null, TestEnum.A, TestEnum.B, TestEnum.C })
Assert.Contains($"// ### Parameter {param} ###" + Environment.NewLine, logger.GetLog());
Assert.DoesNotContain($"// ### Parameter {default(TestEnum)} ###" + Environment.NewLine, logger.GetLog());
}

public class ParamsAllValuesTestNullableEnum
{
[ParamsAllValues]
public TestEnum? ParamProperty { get; set; }

[Benchmark]
public void Benchmark() => Console.WriteLine($"// ### Parameter {ParamProperty} ###");
}

public enum TestEnum
{
A = 1, B, C
}
}

public class ParamsAllValuesTestNotAllowedTypeErrorTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestNotAllowedTypeErrorTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
// System.InvalidOperationException : Type Int32 cannot be used with [ParamsAllValues], allowed types are: bool, enum types and nullable type for another allowed type.
Assert.Throws<InvalidOperationException>(() => CanExecute<ParamsAllValuesTestNotAllowedTypeError>());
}

public class ParamsAllValuesTestNotAllowedTypeError
{
[ParamsAllValues]
public int ParamProperty { get; set; }

[Benchmark]
public void Benchmark() { }
}
}

public class ParamsAllValuesTestNotAllowedNullableTypeErrorTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestNotAllowedNullableTypeErrorTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
// System.InvalidOperationException : Type Int32 cannot be used with [ParamsAllValues], allowed types are: bool, enum types and nullable type for another allowed type.
Assert.Throws<InvalidOperationException>(() => CanExecute<ParamsAllValuesTestNotAllowedTypeError>());
}

public class ParamsAllValuesTestNotAllowedTypeError
{
[ParamsAllValues]
public int? ParamProperty { get; set; }

[Benchmark]
public void Benchmark() { }
}
}

public class ParamsAllValuesTestFlagsEnumErrorTest : BenchmarkTestExecutor
{
public ParamsAllValuesTestFlagsEnumErrorTest(ITestOutputHelper output) : base(output) { }

[Fact]
public void Test()
{
// System.InvalidOperationException : Unable to use TestFlagsEnum with [ParamsAllValues], because it's flags enum.
Assert.Throws<InvalidOperationException>(() => CanExecute<ParamsAllValuesTestFlagsEnumError>());
}

public class ParamsAllValuesTestFlagsEnumError
{
[ParamsAllValues]
public TestFlagsEnum ParamProperty { get; set; }

[Benchmark]
public void Benchmark() { }
}

[Flags]
public enum TestFlagsEnum
{
A = 0b001,
B = 0b010,
C = 0b100
}
}
}