Skip to content

Commit 4d4062b

Browse files
committed
Async refactor WIP
1 parent cb5d392 commit 4d4062b

File tree

52 files changed

+1818
-912
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1818
-912
lines changed

BenchmarkDotNet.slnx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<Folder Name="/samples/">
77
<File Path="samples/Directory.Build.props" />
88
<Project Path="samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj" />
9-
<Project Path="samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj" DefaultStartup="true" />
9+
<Project Path="samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj" />
1010
</Folder>
1111
<Folder Name="/src/">
1212
<Project Path="src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj" />

build/BenchmarkDotNet.Build/Runners/BuildRunner.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using Cake.Common.Tools.DotNet.Workload.Install;
99
using Cake.Core;
1010
using Cake.Core.IO;
11-
using System;
1211
using System.IO;
1312
using System.Linq;
1413

@@ -51,7 +50,6 @@ public void PackWeaver()
5150
{
5251
MSBuildSettings = context.MsBuildSettingsRestore,
5352
};
54-
MaybeAppendArgument(restoreSettings);
5553
context.DotNetRestore(weaverPath.GetDirectory().FullPath, restoreSettings);
5654

5755
context.Information("BuildSystemProvider: " + context.BuildSystem().Provider);
@@ -63,7 +61,6 @@ public void PackWeaver()
6361
Configuration = context.BuildConfiguration,
6462
Verbosity = context.BuildVerbosity
6563
};
66-
MaybeAppendArgument(buildSettings);
6764
context.DotNetBuild(weaverPath.FullPath, buildSettings);
6865

6966
var packSettings = new DotNetPackSettings
@@ -72,7 +69,6 @@ public void PackWeaver()
7269
MSBuildSettings = context.MsBuildSettingsPack,
7370
Configuration = context.BuildConfiguration
7471
};
75-
MaybeAppendArgument(packSettings);
7672
context.DotNetPack(weaverPath.FullPath, packSettings);
7773
}
7874

build/common.props

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

6161
<PropertyGroup>
6262
<!-- Increment this when the BenchmarkDotNet.Weaver package needs to be re-packed. -->
63-
<WeaverVersionSuffix>-1</WeaverVersionSuffix>
63+
<WeaverVersionSuffix>-2</WeaverVersionSuffix>
6464
</PropertyGroup>
6565

6666
<ItemGroup>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace BenchmarkDotNet.Attributes;
4+
5+
/// <summary>
6+
/// When applied to an async benchmark method, overrides the return type of the async method that calls the benchmark method.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Method)]
9+
public sealed class AsyncCallerTypeAttribute(Type asyncCallerType) : Attribute
10+
{
11+
/// <summary>
12+
/// The return type of the async method that calls the benchmark method.
13+
/// </summary>
14+
public Type AsyncCallerType { get; private set; } = asyncCallerType;
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace BenchmarkDotNet.Attributes.CompilerServices;
5+
6+
// MethodImplOptions.AggressiveOptimization is applied to all methods to force them to go straight to tier1 JIT,
7+
// eliminating tiered JIT as a potential variable in measurements.
8+
// This is necessary because C# does not support any way to apply attributes to compiler-generated state machine methods.
9+
// This is applied both to the core Engine and auto-generated classes.
10+
#pragma warning disable CS1574
11+
/// <summary>
12+
/// Instructs the BenchmarkDotNet assembly weaver to apply <see cref="MethodImplOptions.AggressiveOptimization"/> to all declared
13+
/// methods in the annotated type and nested types that are not already annotated with <see cref="MethodImplOptions.NoOptimization"/>.
14+
/// </summary>
15+
#pragma warning restore CS1574
16+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
17+
public sealed class AggressivelyOptimizeMethodsAttribute : Attribute
18+
{
19+
}

src/BenchmarkDotNet.Weaver/buildTransitive/netstandard2.0/BenchmarkDotNet.Weaver.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Inputs="$(BenchmarkDotNetWeaveAssemblyPath)"
1818
Outputs="$(BenchmarkDotNetWeaveAssembliesStampFile)">
1919

20-
<WeaveAssemblyTask TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)" />
20+
<WeaveAssemblyTask TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" />
2121

2222
<!-- Create stamp file for incrementality -->
2323
<Touch Files="$(BenchmarkDotNetWeaveAssembliesStampFile)" AlwaysCreate="true" />
Binary file not shown.
Binary file not shown.

src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,76 @@ public sealed class WeaveAssemblyTask : Task
2424
[Required]
2525
public string TargetAssembly { get; set; }
2626

27+
/// <summary>
28+
/// Whether to treat warnings as errors.
29+
/// </summary>
30+
public bool TreatWarningsAsErrors { get; set; }
31+
2732
/// <summary>
2833
/// Runs the weave assembly task.
2934
/// </summary>
3035
/// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns>
3136
public override bool Execute()
32-
{
37+
{
3338
if (!File.Exists(TargetAssembly))
3439
{
3540
Log.LogError($"Assembly not found: {TargetAssembly}");
3641
return false;
3742
}
3843

39-
4044
bool benchmarkMethodsImplAdjusted = false;
4145
try
4246
{
4347
var module = ModuleDefinition.FromFile(TargetAssembly);
4448

49+
bool anyAdjustments = false;
4550
foreach (var type in module.GetAllTypes())
4651
{
47-
// We can skip non-public types as they are not valid for benchmarks.
48-
if (type.IsNotPublic)
52+
if (type.CustomAttributes.Any(attr => attr.Constructor.DeclaringType.FullName == "BenchmarkDotNet.Attributes.CompilerServices.AggressivelyOptimizeMethodsAttribute"))
4953
{
50-
continue;
54+
ApplyAggressiveOptimizationToMethods(type);
55+
56+
void ApplyAggressiveOptimizationToMethods(TypeDefinition type)
57+
{
58+
// Apply AggressiveOptimization to all methods in the type and nested types that
59+
// aren't annotated with NoOptimization (this includes compiler-generated state machines).
60+
foreach (var method in type.Methods)
61+
{
62+
if ((method.ImplAttributes & MethodImplAttributes.NoOptimization) == 0)
63+
{
64+
var oldImpl = method.ImplAttributes;
65+
method.ImplAttributes |= MethodImplAttributes.AggressiveOptimization;
66+
anyAdjustments |= (oldImpl & MethodImplAttributes.AggressiveOptimization) == 0;
67+
}
68+
}
69+
70+
// Recurse into nested types
71+
foreach (var nested in type.NestedTypes)
72+
{
73+
ApplyAggressiveOptimizationToMethods(nested);
74+
}
75+
}
5176
}
5277

53-
foreach (var method in type.Methods)
78+
// We can skip non-public types as they are not valid for benchmarks.
79+
// !type.IsNotPublic handles nested types, while type.IsPublic does not.
80+
if (!type.IsNotPublic)
5481
{
55-
if (method.CustomAttributes.Any(IsBenchmarkAttribute))
82+
foreach (var method in type.Methods)
5683
{
57-
var oldImpl = method.ImplAttributes;
58-
// Remove AggressiveInlining and add NoInlining.
59-
method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
60-
benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
84+
if (method.CustomAttributes.Any(IsBenchmarkAttribute))
85+
{
86+
var oldImpl = method.ImplAttributes;
87+
// Remove AggressiveInlining and add NoInlining.
88+
method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
89+
benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
90+
anyAdjustments |= benchmarkMethodsImplAdjusted;
91+
}
6192
}
6293
}
6394
}
6495

65-
if (benchmarkMethodsImplAdjusted)
96+
if (anyAdjustments)
6697
{
6798
// Write to a memory stream before overwriting the original file in case an exception occurs during the write (like unsupported platform).
6899
// https://github.com/Washi1337/AsmResolver/issues/640
@@ -90,9 +121,17 @@ public override bool Execute()
90121
}
91122
catch (Exception e)
92123
{
93-
Log.LogWarning($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}. Error:{Environment.NewLine}{e}");
124+
if (TreatWarningsAsErrors)
125+
{
126+
Log.LogError($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}.");
127+
Log.LogErrorFromException(e, true, true, null);
128+
}
129+
else
130+
{
131+
Log.LogWarning($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}. Error:{Environment.NewLine}{e}");
132+
}
94133
}
95-
return true;
134+
return !Log.HasLoggedErrors;
96135
}
97136

98137
private static bool IsBenchmarkAttribute(CustomAttribute attribute)

src/BenchmarkDotNet/BenchmarkDotNet.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<Import Project="..\..\build\common.props" />
3+
34
<PropertyGroup>
45
<AssemblyTitle>BenchmarkDotNet</AssemblyTitle>
56
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
@@ -49,5 +50,10 @@
4950
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
5051
</PackageReference>
5152
</ItemGroup>
53+
<!-- The transitive weaver reference is stripped during full pack, so we need to reference it directly. -->
54+
<ItemGroup Condition="'$(IsFullPack)' == 'true'">
55+
<PackageReference Include="BenchmarkDotNet.Weaver" Version="$(Version)$(WeaverVersionSuffix)" PrivateAssets="all" />
56+
</ItemGroup>
57+
5258
<Import Project="..\..\build\common.targets" />
5359
</Project>

0 commit comments

Comments
 (0)