Skip to content

Commit d758f63

Browse files
authored
Allow for Config per method, introduce OS and OSArchitecture filters (#1097)
1 parent 970d289 commit d758f63

File tree

9 files changed

+269
-55
lines changed

9 files changed

+269
-55
lines changed

samples/BenchmarkDotNet.Samples/IntroMemoryRandomization.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
using BenchmarkDotNet.Attributes;
22
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
73

84
namespace BenchmarkDotNet.Samples
95
{
@@ -23,6 +19,12 @@ public void Setup()
2319
}
2420

2521
[Benchmark]
26-
public void Array() => System.Array.Copy(_array, _destination, Size);
22+
[MemoryRandomization(false)]
23+
public void Array_RandomizationDisabled() => Array.Copy(_array, _destination, Size);
24+
25+
[Benchmark]
26+
[MemoryRandomization(true)]
27+
[MaxIterationCount(40)] // the benchmark becomes multimodal and need a lower limit of max iterations than the default
28+
public void Array_RandomizationEnabled() => Array.Copy(_array, _destination, Size);
2729
}
2830
}

src/BenchmarkDotNet/Attributes/Filters/FilterConfigBaseAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace BenchmarkDotNet.Attributes
66
{
7-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)]
7+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
88
public abstract class FilterConfigBaseAttribute : Attribute, IConfigSource
99
{
1010
// CLS-Compliant Code requires a constructor without an array in the argument list
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Linq;
2+
using BenchmarkDotNet.Filters;
3+
using JetBrains.Annotations;
4+
using System.Runtime.InteropServices;
5+
6+
namespace BenchmarkDotNet.Attributes
7+
{
8+
[PublicAPI]
9+
public class OperatingSystemsArchitectureFilterAttribute : FilterConfigBaseAttribute
10+
{
11+
// CLS-Compliant Code requires a constructor without an array in the argument list
12+
public OperatingSystemsArchitectureFilterAttribute() { }
13+
14+
/// <param name="allowed">if set to true, the architectures are enabled, if set to false, disabled</param>
15+
public OperatingSystemsArchitectureFilterAttribute(bool allowed, params Architecture[] architectures)
16+
: base(new SimpleFilter(_ =>
17+
{
18+
return allowed
19+
? architectures.Any(architecture => RuntimeInformation.OSArchitecture == architecture)
20+
: architectures.All(architecture => RuntimeInformation.OSArchitecture != architecture);
21+
}))
22+
{
23+
}
24+
}
25+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Linq;
3+
using BenchmarkDotNet.Filters;
4+
using JetBrains.Annotations;
5+
using System.Runtime.InteropServices;
6+
7+
namespace BenchmarkDotNet.Attributes
8+
{
9+
public enum OS : byte
10+
{
11+
Windows,
12+
Linux,
13+
macOS,
14+
/// <summary>
15+
/// WebAssembly
16+
/// </summary>
17+
Browser
18+
}
19+
20+
[PublicAPI]
21+
public class OperatingSystemsFilterAttribute : FilterConfigBaseAttribute
22+
{
23+
private static readonly OSPlatform browser = OSPlatform.Create("BROWSER");
24+
25+
// CLS-Compliant Code requires a constructor without an array in the argument list
26+
public OperatingSystemsFilterAttribute() { }
27+
28+
/// <param name="allowed">if set to true, the OSes beloning to platforms are enabled, if set to false, disabled</param>
29+
public OperatingSystemsFilterAttribute(bool allowed, params OS[] platforms)
30+
: base(new SimpleFilter(_ =>
31+
{
32+
return allowed
33+
? platforms.Any(platform => RuntimeInformation.IsOSPlatform(Map(platform)))
34+
: platforms.All(platform => !RuntimeInformation.IsOSPlatform(Map(platform)));
35+
}))
36+
{
37+
}
38+
39+
// OSPlatform is a struct so it can not be used as attribute argument and this is why we use PlatformID enum
40+
private static OSPlatform Map(OS platform)
41+
{
42+
switch (platform)
43+
{
44+
case OS.Windows:
45+
return OSPlatform.Windows;
46+
case OS.Linux:
47+
return OSPlatform.Linux;
48+
case OS.macOS:
49+
return OSPlatform.OSX;
50+
case OS.Browser:
51+
return browser;
52+
default:
53+
throw new NotSupportedException($"Platform {platform} is not supported");
54+
}
55+
}
56+
}
57+
}

src/BenchmarkDotNet/Attributes/Mutators/JobMutatorConfigBaseAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace BenchmarkDotNet.Attributes
77
{
8-
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // users must not be able to define given mutator attribute more than once per type
8+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] // users must not be able to define given mutator attribute more than once per type
99
public class JobMutatorConfigBaseAttribute : Attribute, IConfigSource
1010
{
1111
// CLS-Compliant Code requires a constructor which use only CLS-compliant types

src/BenchmarkDotNet/Running/BenchmarkConverter.cs

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,78 +23,78 @@ public static BenchmarkRunInfo TypeToBenchmarks(Type type, IConfig config = null
2323

2424
// We should check all methods including private to notify users about private methods with the [Benchmark] attribute
2525
var bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
26+
var benchmarkMethods = type.GetMethods(bindingFlags).Where(method => method.HasAttribute<BenchmarkAttribute>()).ToArray();
2627

27-
var fullConfig = GetFullConfig(type, config);
28-
var allMethods = type.GetMethods(bindingFlags);
29-
return MethodsToBenchmarksWithFullConfig(type, allMethods, fullConfig);
28+
return MethodsToBenchmarksWithFullConfig(type, benchmarkMethods, config);
3029
}
3130

3231
public static BenchmarkRunInfo MethodsToBenchmarks(Type containingType, MethodInfo[] benchmarkMethods, IConfig config = null)
33-
{
34-
var fullConfig = GetFullConfig(containingType, config);
35-
36-
return MethodsToBenchmarksWithFullConfig(containingType, benchmarkMethods, fullConfig);
37-
}
32+
=> MethodsToBenchmarksWithFullConfig(containingType, benchmarkMethods, config);
3833

39-
private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type containingType, MethodInfo[] benchmarkMethods, ImmutableConfig immutableConfig)
34+
private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type type, MethodInfo[] benchmarkMethods, IConfig config)
4035
{
41-
if (immutableConfig == null)
42-
throw new ArgumentNullException(nameof(immutableConfig));
43-
44-
var helperMethods = containingType.GetMethods(); // benchmarkMethods can be filtered, without Setups, look #564
45-
46-
var globalSetupMethods = GetAttributedMethods<GlobalSetupAttribute>(helperMethods, "GlobalSetup");
47-
var globalCleanupMethods = GetAttributedMethods<GlobalCleanupAttribute>(helperMethods, "GlobalCleanup");
48-
var iterationSetupMethods = GetAttributedMethods<IterationSetupAttribute>(helperMethods, "IterationSetup");
49-
var iterationCleanupMethods = GetAttributedMethods<IterationCleanupAttribute>(helperMethods, "IterationCleanup");
50-
51-
var targetMethods = benchmarkMethods.Where(method => method.HasAttribute<BenchmarkAttribute>()).ToArray();
36+
var allPublicMethods = type.GetMethods(); // benchmarkMethods can be filtered, without Setups, look #564
37+
var configPerType = GetFullTypeConfig(type, config);
5238

53-
var parameterDefinitions = GetParameterDefinitions(containingType);
54-
var parameterInstancesList = parameterDefinitions.Expand(immutableConfig.SummaryStyle);
39+
var globalSetupMethods = GetAttributedMethods<GlobalSetupAttribute>(allPublicMethods, "GlobalSetup");
40+
var globalCleanupMethods = GetAttributedMethods<GlobalCleanupAttribute>(allPublicMethods, "GlobalCleanup");
41+
var iterationSetupMethods = GetAttributedMethods<IterationSetupAttribute>(allPublicMethods, "IterationSetup");
42+
var iterationCleanupMethods = GetAttributedMethods<IterationCleanupAttribute>(allPublicMethods, "IterationCleanup");
5543

56-
var jobs = immutableConfig.GetJobs();
44+
var targets = GetTargets(benchmarkMethods, type, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods).ToArray();
5745

58-
var targets = GetTargets(targetMethods, containingType, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods).ToArray();
46+
var parameterDefinitions = GetParameterDefinitions(type);
47+
var parameterInstancesList = parameterDefinitions.Expand(configPerType.SummaryStyle);
5948

6049
var benchmarks = new List<BenchmarkCase>();
50+
6151
foreach (var target in targets)
6252
{
63-
var argumentsDefinitions = GetArgumentsDefinitions(target.WorkloadMethod, target.Type, immutableConfig.SummaryStyle).ToArray();
53+
var argumentsDefinitions = GetArgumentsDefinitions(target.WorkloadMethod, target.Type, configPerType.SummaryStyle).ToArray();
6454

6555
var parameterInstances =
6656
(from parameterInstance in parameterInstancesList
6757
from argumentDefinition in argumentsDefinitions
6858
select new ParameterInstances(parameterInstance.Items.Concat(argumentDefinition.Items).ToArray())).ToArray();
6959

70-
benchmarks.AddRange(
71-
from job in jobs
60+
var configPerMethod = GetFullMethodConfig(target.WorkloadMethod, configPerType);
61+
62+
var benchmarksForTarget =
63+
from job in configPerMethod.GetJobs()
7264
from parameterInstance in parameterInstances
73-
select BenchmarkCase.Create(target, job, parameterInstance, immutableConfig)
74-
);
65+
select BenchmarkCase.Create(target, job, parameterInstance, configPerMethod);
66+
67+
benchmarks.AddRange(GetFilteredBenchmarks(benchmarksForTarget, configPerMethod.GetFilters()));
7568
}
7669

77-
var filters = immutableConfig.GetFilters().ToArray();
78-
var filteredBenchmarks = GetFilteredBenchmarks(benchmarks, filters);
79-
var orderedBenchmarks = immutableConfig.Orderer.GetExecutionOrder(filteredBenchmarks).ToArray();
70+
var orderedBenchmarks = configPerType.Orderer.GetExecutionOrder(benchmarks.ToImmutableArray()).ToArray();
8071

81-
return new BenchmarkRunInfo(orderedBenchmarks, containingType, immutableConfig);
72+
return new BenchmarkRunInfo(orderedBenchmarks, type, configPerType);
8273
}
8374

84-
public static ImmutableConfig GetFullConfig(Type type, IConfig config)
75+
private static ImmutableConfig GetFullTypeConfig(Type type, IConfig config)
8576
{
8677
config = config ?? DefaultConfig.Instance;
87-
if (type != null)
88-
{
89-
var typeAttributes = type.GetTypeInfo().GetCustomAttributes(true).OfType<IConfigSource>();
90-
var assemblyAttributes = type.GetTypeInfo().Assembly.GetCustomAttributes().OfType<IConfigSource>();
91-
var allAttributes = typeAttributes.Concat(assemblyAttributes);
92-
var configs = allAttributes.Select(attribute => attribute.Config)
93-
.OrderBy(c => c.GetJobs().Count(job => job.Meta.IsMutator)); // configs with mutators must be the ones applied at the end
94-
95-
foreach (var configFromAttribute in configs)
96-
config = ManualConfig.Union(config, configFromAttribute);
97-
}
78+
79+
var typeAttributes = type.GetCustomAttributes(true).OfType<IConfigSource>();
80+
var assemblyAttributes = type.Assembly.GetCustomAttributes().OfType<IConfigSource>();
81+
82+
foreach (var configFromAttribute in typeAttributes.Concat(assemblyAttributes))
83+
config = ManualConfig.Union(config, configFromAttribute.Config);
84+
85+
return ImmutableConfigBuilder.Create(config);
86+
}
87+
88+
private static ImmutableConfig GetFullMethodConfig(MethodInfo method, ImmutableConfig typeConfig)
89+
{
90+
var methodAttributes = method.GetCustomAttributes(true).OfType<IConfigSource>();
91+
92+
if (!methodAttributes.Any()) // the most common case
93+
return typeConfig;
94+
95+
var config = ManualConfig.Create(typeConfig);
96+
foreach (var configFromAttribute in methodAttributes)
97+
config = ManualConfig.Union(config, configFromAttribute.Config);
9898

9999
return ImmutableConfigBuilder.Create(config);
100100
}
@@ -251,7 +251,7 @@ private static string[] GetCategories(MethodInfo method)
251251
return attributes.SelectMany(attr => attr.Categories).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
252252
}
253253

254-
private static ImmutableArray<BenchmarkCase> GetFilteredBenchmarks(IList<BenchmarkCase> benchmarks, IList<IFilter> filters)
254+
private static ImmutableArray<BenchmarkCase> GetFilteredBenchmarks(IEnumerable<BenchmarkCase> benchmarks, IEnumerable<IFilter> filters)
255255
=> benchmarks.Where(benchmark => filters.All(filter => filter.Predicate(benchmark))).ToImmutableArray();
256256

257257
private static void AssertMethodHasCorrectSignature(string methodType, MethodInfo methodInfo)

src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class BenchmarkPartitioner
1313
{
1414
public static BuildPartition[] CreateForBuild(BenchmarkRunInfo[] supportedBenchmarks, IResolver resolver)
1515
=> supportedBenchmarks
16-
.SelectMany(info => info.BenchmarksCases.Select(benchmark => (benchmark, info.Config)))
16+
.SelectMany(info => info.BenchmarksCases.Select(benchmark => (benchmark, benchmark.Config)))
1717
.GroupBy(tuple => tuple.benchmark, BenchmarkRuntimePropertiesComparer.Instance)
1818
.Select(group => new BuildPartition(group.Select((item, index) => new BenchmarkBuildInfo(item.benchmark, item.Config, index)).ToArray(), resolver))
1919
.ToArray();

tests/BenchmarkDotNet.IntegrationTests/BenchmarkTestExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected Reports.Summary CanExecute(Type type, IConfig config = null, bool full
6060
config = config.AddColumnProvider(DefaultColumnProviders.Instance);
6161

6262
// Make sure we ALWAYS combine the Config (default or passed in) with any Config applied to the Type/Class
63-
var summary = BenchmarkRunner.Run(type, BenchmarkConverter.GetFullConfig(type, config));
63+
var summary = BenchmarkRunner.Run(type, config);
6464

6565
if (fullValidation)
6666
{

0 commit comments

Comments
 (0)