Skip to content

Commit c14e7c4

Browse files
DrewScogginsagockeCopilot
authored
Update bdn 0.16 binarydata and async (#5217)
* Enable runtime-async for net11.0+ * Update BenchmarkDotNet to 0.16.0-nightly.20260518.1249 Bumps BenchmarkDotNet to the latest master nightly, built from dotnet/BenchmarkDotNet@c7632225 ("Disassembly follow jump trampolines (#3136)") and pushed to the benchmark-dotnet-prerelease internal feed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate BenchmarkDotNet.Extensions to BDN 0.16.0 APIs BDN 0.16.0 ('Async Refactor', PR #2958) made breaking API changes that the harness still consumed: - ExporterBase.ExportToLog(Summary, ILogger) was removed; the new abstract ExportAsync uses an internal CancelableStreamWriter that subclasses can't satisfy. PerfLabExporter now implements IExporter directly. - IValidator.Validate returning IEnumerable<ValidationError> was replaced with ValidateAsync returning IAsyncEnumerable<ValidationError>. The four validators (UniqueArguments, TooManyTestCases, NoWasm, MandatoryCategory) use .ToAsyncEnumerable() (transitively from BDN's System.Linq.AsyncEnumerable dep), not async + yield return - the latter produces an AsyncIteratorMethodBuilder state machine that deadlocks with BDN's BenchmarkSynchronizationContext. Switched BenchmarkDotNet.Extensions from netstandard2.0 to net8.0. The netstandard2.0 target was only used to enable an opt-in net472 path, but PERFLAB_TARGET_FRAMEWORKS=net472 is no longer exercised in CI and the new BDN APIs would otherwise require polyfill packages. Removed the net472 package conditional from MicroBenchmarks.csproj and the net472/.NET Framework references from docs/benchmarkdotnet.md and the micro README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix nullability errors surfaced by net8.0 retarget Switching BenchmarkDotNet.Extensions from netstandard2.0 to net8.0 brought in stricter nullable annotations from the BCL and BDN package, which combined with TreatWarningsAsErrors=true (set in src/Directory.Build.props) turned previously-hidden nullability warnings into build errors. - ValuesGenerator.Dictionary<TKey, TValue>: add 'where TKey : notnull' constraint required by Dictionary<,>. - UniqueArgumentsValidator.BenchmarkArgumentsComparer.Equals: match the IEqualityComparer<T>.Equals(T?, T?) interface signature; handle nulls. - TooManyTestCasesValidator.SkipValidation: parameter must be MemberInfo? since MemberInfo.DeclaringType returns Type?. - DiffableDisassemblyExporter: add null-forgiving (!) on reflection lookups whose return values are intentionally trusted by this type (it's a copy of internal BDN code that operates on known types). - PerfLabExporter: BuildJson can return null; use string?. Verified by running the exact CI commands locally (dotnet restore + build with --framework net11.0) using the SDK from global.json. Build succeeds with 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Switch micro entrypoint to RunAsync to avoid discovery-time deadlock BDN 0.16's sync entrypoints (BenchmarkSwitcher.Run / BenchmarkRunner.Run) install BenchmarkDotNetSynchronizationContext (a single-threaded message pump) before benchmark discovery. Discovery executes [ParamsSource] and [ArgumentsSource] callbacks; some perf-repo callbacks do sync-over-async (notably SslStreamTests.GetTls13Support, which calls HandshakeAsync(...) .GetAwaiter().GetResult()). Sync-over-async deadlocks on the single-threaded SyncCtx because the awaited continuation is queued back to a pump that the caller is blocking. Switch Program.Main to async Task<int> and await BenchmarkSwitcher.RunAsync. The async entrypoint never installs BenchmarkDotNetSynchronizationContext on the caller, so discovery runs on the default context and sync-over-async in source callbacks no longer deadlocks. Real benchmark execution still gets the SyncCtx semantics it needs because BDN installs it inside the per-benchmark execute path, not on the entrypoint thread. This is the supported BDN-recommended fix for the discovery-deadlock symptom; no BDN code change is required. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Gate runtime-async behind 'runtimeasync' experiment PR #5195 enabled the runtime-async language feature unconditionally for net11.0+ via `<Features>`. To avoid affecting baseline measurement runs, gate it behind the existing experiment infrastructure so it only takes effect in the dedicated experiment lane. - src/Directory.Build.targets: only set `runtime-async=on` when the `EnableRuntimeAsync` MSBuild property is `true` (in addition to the existing TFM check). - scripts/ci_setup.py: when `--experiment-name=runtimeasync` is passed, emit `EnableRuntimeAsync=true` as an env var so MSBuild picks it up as a property (matches the pattern used by the `jitoptrepeat` experiment). Verified locally: BDN dry runs against the BinaryDataPayload tests succeed both with and without the env var (18/18 benchmarks each). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Andy Gocke <andy@commentout.net> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3ffc1d0 commit c14e7c4

18 files changed

Lines changed: 125 additions & 66 deletions

docs/benchmarkdotnet.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ M00_L00:
289289

290290
The `--runtimes` or just `-r` allows you to run the benchmarks for **multiple Runtimes**.
291291

292-
Available options are: Mono, wasmnet70, CoreRT, net462, net47, net471, net472, netcoreapp3.1, net6.0, net7.0, net8.0, and net9.0.
292+
Available options are: Mono, wasmnet70, CoreRT, netcoreapp3.1, net6.0, net7.0, net8.0, and net9.0.
293293

294294
Example: run the benchmarks for .NET 7.0 and 8.0:
295295

@@ -361,18 +361,6 @@ dotnet run -c Release -f net9.0 --cli "C:\Projects\performance\.dotnet\dotnet.ex
361361

362362
This is very useful when you want to compare different builds of .NET.
363363

364-
### Private CLR Build
365-
366-
It's possible to benchmark a private build of .NET Runtime. You just need to pass the value of `COMPLUS_Version` to BenchmarkDotNet. You can do that by either using `--clrVersion $theVersion` as an argument or `Job.ShortRun.With(new ClrRuntime(version: "$theVersion"))` in the code.
367-
368-
So if you made a change in CLR and want to measure the difference, you can run the benchmarks with:
369-
370-
```cmd
371-
dotnet run -c Release -f net48 -- --clrVersion $theVersion
372-
```
373-
374-
More info can be found in [BenchmarkDotNet issue #706](https://github.com/dotnet/BenchmarkDotNet/issues/706).
375-
376364
### Private CoreRT Build
377365

378366
To run benchmarks with private CoreRT build you need to provide the `IlcPath`. Example:

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<MicrosoftNETILLinkPackageVersion>11.0.0-preview.5.26261.101</MicrosoftNETILLinkPackageVersion>
1212
<SystemThreadingChannelsPackageVersion>11.0.0-preview.5.26261.101</SystemThreadingChannelsPackageVersion>
1313
<MicrosoftExtensionsLoggingPackageVersion>11.0.0-preview.5.26261.101</MicrosoftExtensionsLoggingPackageVersion>
14-
<BenchmarkDotNetVersion>0.16.0-nightly.20260320.467</BenchmarkDotNetVersion>
14+
<BenchmarkDotNetVersion>0.16.0-nightly.20260518.1249</BenchmarkDotNetVersion>
1515
<MicrosoftNETRuntimeEmscripten3156Nodewinx64Version>11.0.0-preview.5.26261.101</MicrosoftNETRuntimeEmscripten3156Nodewinx64Version>
1616
<MicrosoftDotNetXHarnessCLIVersion>11.0.0-prerelease.26204.1</MicrosoftDotNetXHarnessCLIVersion>
1717
</PropertyGroup>

scripts/ci_setup.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,11 @@ def main(args: CiSetupArgs):
424424
if args.experiment_name == "jitoptrepeat":
425425
experiment_config = variable_format % ('DOTNET_JitOptRepeat', '*')
426426

427+
if args.experiment_name == "runtimeasync":
428+
# Surfaced to MSBuild as the $(EnableRuntimeAsync) property; gates the
429+
# runtime-async Features flag in src/Directory.Build.targets.
430+
experiment_config = variable_format % ('EnableRuntimeAsync', 'true')
431+
427432
output = ''
428433

429434
with push_dir(get_repo_root_path()):

src/Directory.Build.props

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,27 @@
55
<!-- Avoid spawning any-long living compiler processes to avoid BenchmarkDotNet issues with "file in use",
66
and automation failing to clean up the folders once the runs are over -->
77
<UseSharedCompilation>false</UseSharedCompilation>
8-
8+
99
<!-- Use the latest C# compiler features -->
1010
<LangVersion>latest</LangVersion>
11-
11+
1212
<!-- This repo does not produce any libraries, therefore generating docs is disabled -->
1313
<GenerateDocumentationFile>False</GenerateDocumentationFile>
14-
14+
1515
<!-- every warning is important, we want to enforce best practices here to keep high quality of the code and avoid common mistakes -->
1616
<NoWarn>$(NoWarn);NU1507</NoWarn> <!-- Darc does not seem to support auto updating of packageSourceMapping causing tool publishes to fail without manual updating of the NuGet.config with package mappings, disable this check so we don't have to manually update the source mappings. -->
1717
<NoWarn>$(NoWarn);NETSDK1138</NoWarn> <!-- Disable warning about EOL target frameworks as we target them to test them -->
1818
<NoWarn>$(NoWarn);CS9057</NoWarn> <!-- Suppress analyzer version mismatch when BDN analyzers target a newer Roslyn than the SDK provides -->
1919
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
2020
<WarningLevel>4</WarningLevel>
21-
21+
2222
<!-- we are testing latest bits and we must use preview SDK version -->
2323
<SuppressNETCoreSdkPreviewMessage>True</SuppressNETCoreSdkPreviewMessage>
2424

2525
<!-- Disable SourceLink -->
2626
<EnableSourceLink>false</EnableSourceLink>
2727
<EnableSourceControlManagerQueries>false</EnableSourceControlManagerQueries>
28-
28+
2929
<!-- Explicit disable signing the built assemblies here to stop Arcade attempting to sign them -->
3030
<SignAssembly>false</SignAssembly>
3131
</PropertyGroup>

src/Directory.Build.targets

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project>
2+
<Import Project="../Directory.Build.targets" />
3+
4+
<PropertyGroup>
5+
<!-- Runtime-async is gated behind the 'runtimeasync' experiment so it only runs
6+
in the dedicated experiment lane. ci_setup.py sets EnableRuntimeAsync=true as
7+
an env var (which MSBuild surfaces as a property) when the experiment name
8+
is 'runtimeasync'. -->
9+
<Features Condition="'$(EnableRuntimeAsync)' == 'true' and $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net11.0'))">$(Features);runtime-async=on</Features>
10+
</PropertyGroup>
11+
</Project>

src/benchmarks/micro/MicroBenchmarks.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,6 @@
122122
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(ExtensionsVersion)" />
123123
<PackageReference Include="Microsoft.Extensions.Options" Version="$(ExtensionsVersion)" />
124124
</ItemGroup>
125-
<!-- These package versions are only for the opt-in net472 path (for example, PERFLAB_TARGET_FRAMEWORKS=net472). -->
126-
<ItemGroup Condition="'$(TargetFramework)' != '' and '$(TargetFrameworkIdentifier)' == '.NETFramework'">
127-
<PackageReference Include="System.Memory" Version="4.6.3" />
128-
<PackageReference Include="System.Numerics.Vectors" Version="4.6.1" />
129-
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
130-
<PackageReference Include="System.IO.Pipelines" Version="10.0.3" />
131-
<PackageReference Include="System.Threading.Channels" Version="$(SystemVersion)" />
132-
</ItemGroup>
133125
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0')) and !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">
134126
<!-- Newer System.Numerics.Tensors packages require net10+, so keep net8.0/net9.0 on the last compatible package line. -->
135127
<PackageReference Include="System.Numerics.Tensors" Version="10.0.*-*" />

src/benchmarks/micro/Program.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
8+
using System.Threading.Tasks;
89
using BenchmarkDotNet.Running;
910
using System.IO;
1011
using BenchmarkDotNet.Extensions;
@@ -14,7 +15,7 @@ namespace MicroBenchmarks
1415
{
1516
class Program
1617
{
17-
static int Main(string[] args)
18+
static async Task<int> Main(string[] args)
1819
{
1920
var argsList = new List<string>(args);
2021
int? partitionCount;
@@ -40,9 +41,14 @@ static int Main(string[] args)
4041
return 1;
4142
}
4243

43-
return BenchmarkSwitcher
44+
// Use RunAsync (not Run) so BDN does not install its single-threaded
45+
// BenchmarkDotNetSynchronizationContext on the entrypoint thread. The sync
46+
// entrypoint installs that context before benchmark discovery, which
47+
// deadlocks any sync-over-async work performed by [ParamsSource]/[ArgumentsSource]
48+
// callbacks (e.g. SslStreamTests.GetTls13Support).
49+
var summaries = await BenchmarkSwitcher
4450
.FromAssembly(typeof(Program).Assembly)
45-
.Run(argsList.ToArray(),
51+
.RunAsync(argsList.ToArray(),
4652
RecommendedConfig.Create(
4753
artifactsPath: new DirectoryInfo(Path.Combine(AppContext.BaseDirectory, "BenchmarkDotNet.Artifacts")),
4854
mandatoryCategories: ImmutableHashSet.Create([Categories.Libraries, Categories.Runtime, Categories.ThirdParty, Categories.Sve]),
@@ -52,7 +58,9 @@ static int Main(string[] args)
5258
categoryExclusionFilterValue: categoryExclusionFilterValue,
5359
getDiffableDisasm: getDiffableDisasm)
5460
.AddValidator(new NoWasmValidator(Categories.NoWASM)))
55-
.ToExitCode();
61+
.ConfigureAwait(false);
62+
63+
return summaries.ToExitCode();
5664
}
5765
}
5866
}

src/benchmarks/micro/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ To learn more about designing benchmarks, please read [Microbenchmark Design Gui
1212

1313
## Quick Start
1414

15-
The first thing that you need to choose is the Target Framework. Available options are: `netcoreapp3.1|net6.0|net7.0|net8.0|net9.0|net10.0|net11.0|net472`. You can specify the target framework using `-f|--framework` argument. For the sake of simplicity, all examples below use `net11.0` as the target framework.
15+
The first thing that you need to choose is the Target Framework. Available options are: `net8.0|net9.0|net10.0|net11.0`. You can specify the target framework using `-f|--framework` argument. For the sake of simplicity, all examples below use `net11.0` as the target framework.
1616

1717
The following commands are run from the `src/benchmarks/micro` directory.
1818

src/harness/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Library</OutputType>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<!-- net8.0 is the lowest TFM exercised by the perf pipelines (see scripts/channel_map.py).
5+
net472 (and other netfx) consumers are no longer supported; the harness uses BDN APIs
6+
(e.g. IValidator.ValidateAsync returning IAsyncEnumerable<T>) that would otherwise require
7+
polyfill packages on netstandard2.0. -->
8+
<TargetFramework>net8.0</TargetFramework>
59
<Nullable>enable</Nullable>
610
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
711
</PropertyGroup>
@@ -13,7 +17,7 @@
1317
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="$(BenchmarkDotNetVersion)" />
1418
</ItemGroup>
1519
<ItemGroup Condition="'$(BenchmarkDotNetSources)' != ''">
16-
<ProjectReference Include="$(_BenchmarkDotNetSourcesN)src\BenchmarkDotNet\BenchmarkDotNet.csproj" SetTargetFramework="TargetFramework=netstandard2.0" />
20+
<ProjectReference Include="$(_BenchmarkDotNetSourcesN)src\BenchmarkDotNet\BenchmarkDotNet.csproj" SetTargetFramework="TargetFramework=net8.0" />
1721
<ProjectReference Include="$(_BenchmarkDotNetSourcesN)src\BenchmarkDotNet.Diagnostics.Windows\BenchmarkDotNet.Diagnostics.Windows.csproj" SetTargetFramework="TargetFramework=netstandard2.0" />
1822
</ItemGroup>
1923
<ItemGroup>

src/harness/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,26 @@ internal static string BuildDisassemblyString(DisassemblyResult disassemblyResul
6161

6262
private static Func<object, T> GetElementGetter<T>(string name)
6363
{
64-
var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier");
64+
var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier")!;
6565

66-
type = type.GetNestedType("Element", BindingFlags.Instance | BindingFlags.NonPublic);
66+
type = type.GetNestedType("Element", BindingFlags.Instance | BindingFlags.NonPublic)!;
6767

68-
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic);
68+
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic)!;
6969

70-
var method = property.GetGetMethod(nonPublic: true);
70+
var method = property.GetGetMethod(nonPublic: true)!;
7171

7272
var generic = typeof(Func<,>).MakeGenericType(type, typeof(T));
7373

7474
var @delegate = method.CreateDelegate(generic);
7575

76-
return (obj) => (T)@delegate.DynamicInvoke(obj); // cast to (Func<object, T>) throws
76+
return (obj) => (T)@delegate.DynamicInvoke(obj)!; // cast to (Func<object, T>) throws
7777
}
7878

7979
private static Func<DisassembledMethod, DisassemblyResult, DisassemblyDiagnoserConfig, string, IReadOnlyList<object>> GetPrettifyMethod()
8080
{
81-
var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier");
81+
var type = typeof(DisassemblyDiagnoser).Assembly.GetType("BenchmarkDotNet.Disassemblers.Exporters.DisassemblyPrettifier")!;
8282

83-
var method = type.GetMethod("Prettify", BindingFlags.Static | BindingFlags.NonPublic);
83+
var method = type.GetMethod("Prettify", BindingFlags.Static | BindingFlags.NonPublic)!;
8484

8585
var @delegate = method.CreateDelegate(typeof(Func<DisassembledMethod, DisassemblyResult, DisassemblyDiagnoserConfig, string, IReadOnlyList<object>>));
8686

0 commit comments

Comments
 (0)