From d487799023830b950d95e3a6e5109c3498038bef Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Sat, 24 Feb 2018 14:51:09 +0100 Subject: [PATCH] Added [ParamsAllValues] --- docs/guide/Advanced/Params.md | 46 +++++ .../Intro/IntroParamsAllValues.cs | 27 +++ .../Attributes/ParamsAllValuesAttribute.cs | 9 + .../Running/BenchmarkConverter.cs | 49 +++-- .../ParamsAllValuesTest.cs | 191 ++++++++++++++++++ 5 files changed, 309 insertions(+), 13 deletions(-) create mode 100644 samples/BenchmarkDotNet.Samples/Intro/IntroParamsAllValues.cs create mode 100644 src/BenchmarkDotNet.Core/Attributes/ParamsAllValuesAttribute.cs create mode 100644 tests/BenchmarkDotNet.IntegrationTests/ParamsAllValuesTest.cs diff --git a/docs/guide/Advanced/Params.md b/docs/guide/Advanced/Params.md index 882871d527..c29263de2b 100644 --- a/docs/guide/Advanced/Params.md +++ b/docs/guide/Advanced/Params.md @@ -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`, 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 | \ No newline at end of file diff --git a/samples/BenchmarkDotNet.Samples/Intro/IntroParamsAllValues.cs b/samples/BenchmarkDotNet.Samples/Intro/IntroParamsAllValues.cs new file mode 100644 index 0000000000..b81fb984b8 --- /dev/null +++ b/samples/BenchmarkDotNet.Samples/Intro/IntroParamsAllValues.cs @@ -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)); + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Core/Attributes/ParamsAllValuesAttribute.cs b/src/BenchmarkDotNet.Core/Attributes/ParamsAllValuesAttribute.cs new file mode 100644 index 0000000000..46e967dcdb --- /dev/null +++ b/src/BenchmarkDotNet.Core/Attributes/ParamsAllValuesAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ParamsAllValuesAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs b/src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs index 9dc49142f3..33b27d8fab 100644 --- a/src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs +++ b/src/BenchmarkDotNet.Core/Running/BenchmarkConverter.cs @@ -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(reflectionFlags); + IEnumerable GetDefinitions(Func getValidValues) where TAttribute : Attribute + { + const BindingFlags reflectionFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; - var allParamsSourceMembers = type.GetTypeMembersWithGivenAttribute(reflectionFlags); + var allMembers = type.GetTypeMembersWithGivenAttribute(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((attribute, parameterType) => GetValidValues(attribute.Values, parameterType)); + + var paramsSourceDefinitions = GetDefinitions((attribute, _) => GetValidValuesForParamsSource(type, attribute.Name)); + + var paramsAllValuesDefinitions = GetDefinitions((_, paramaterType) => GetAllValidValues(paramaterType)); + + var definitions = paramsDefinitions.Concat(paramsSourceDefinitions).Concat(paramsAllValuesDefinitions).ToArray(); return new ParameterDefinitions(definitions); } @@ -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().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."); + } } } diff --git a/tests/BenchmarkDotNet.IntegrationTests/ParamsAllValuesTest.cs b/tests/BenchmarkDotNet.IntegrationTests/ParamsAllValuesTest.cs new file mode 100644 index 0000000000..2486d33f8d --- /dev/null +++ b/tests/BenchmarkDotNet.IntegrationTests/ParamsAllValuesTest.cs @@ -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(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(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(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(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(() => CanExecute()); + } + + 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(() => CanExecute()); + } + + 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(() => CanExecute()); + } + + public class ParamsAllValuesTestFlagsEnumError + { + [ParamsAllValues] + public TestFlagsEnum ParamProperty { get; set; } + + [Benchmark] + public void Benchmark() { } + } + + [Flags] + public enum TestFlagsEnum + { + A = 0b001, + B = 0b010, + C = 0b100 + } + } +} \ No newline at end of file