Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/articles/guides/console-args.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ dotnet run -c Release -- --filter * --runtimes net6.0 net8.0 --statisticalTest 5
* `--maxIterationCount` Maximum number of iterations to run. The default is 100.
* `--invocationCount` Invocation count in a single iteration. By default calculated by the heuristic.
* `--unrollFactor` How many times the benchmark method will be invoked per one iteration of a generated loop. 16 by default
* `--consumeTasksSynchronously` (Default: false) Specifies whether to consume (Value)Task-returning benchmarks synchronously.
* `--strategy` The RunStrategy that should be used. Throughput/ColdStart/Monitoring.
* `--platform` The Platform that should be used. If not specified, the host process platform is used (default). AnyCpu/X86/X64/Arm/Arm64/LoongArch64.
* `--runOncePerIteration` (Default: false) Run the benchmark exactly once per iteration.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using BenchmarkDotNet.Jobs;

namespace BenchmarkDotNet.Attributes;

/// <inheritdoc cref="RunMode.ConsumeTasksSynchronously"/>
public class ConsumeTasksSynchronouslyAttribute(bool value) : JobMutatorConfigBaseAttribute(Job.Default.WithConsumeTasksSynchronously(value))
{
}
7 changes: 6 additions & 1 deletion src/BenchmarkDotNet/Code/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ private static DeclarationsProvider GetDeclarationsProvider(BenchmarkCase benchm

if (method.ReturnType.IsAwaitable(out var awaitableInfo))
{
if (benchmark.Job.ResolveValue(RunMode.ConsumeTasksSynchronouslyCharacteristic, EnvironmentResolver.Instance)
&& AwaitHelper.IsBuiltInTaskType(method.ReturnType))
{
return new SyncTaskDeclarationsProvider(benchmark);
}
return new AsyncDeclarationsProvider(benchmark, awaitableInfo.ResultType);
}

Expand Down Expand Up @@ -406,7 +411,7 @@ private static string GetBenchmarkRunCall(BuildPartition buildPartition, CodeGen
.GetType($"BenchmarkDotNet.Autogenerated.Runnable_{id}")
.GetMethod("Run", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Static)
.Invoke(null, new global::System.Object[] { host, benchmarkName, diagnoserRunMode }))
.ConfigureAwait(true);
.ConfigureAwait(false);
""";
}

Expand Down
94 changes: 79 additions & 15 deletions src/BenchmarkDotNet/Code/DeclarationsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Perfolizer.Horology;
Expand Down Expand Up @@ -89,6 +90,24 @@ private static string GetMethodPrefix(MethodInfo method)
protected string GetWorkloadMethodCall(string passArguments)
=> $"{GetMethodPrefix(Descriptor.WorkloadMethod)}.{Descriptor.WorkloadMethod.Name}({passArguments});";

protected string GetLoadArguments()
=> string.Join(
Environment.NewLine,
Descriptor.WorkloadMethod.GetParameters()
.Select((parameter, index) =>
{
var refModifier = parameter.ParameterType.IsByRef ? "ref" : string.Empty;
return $"{refModifier} {parameter.ParameterType.GetCorrectCSharpTypeName()} arg{index} = {refModifier} this.__fieldsContainer.argField{index};";
})
);

protected string GetPassArguments()
=> string.Join(
", ",
Descriptor.WorkloadMethod.GetParameters()
.Select((parameter, index) => $"{CodeGenerator.GetParameterModifier(parameter)} arg{index}")
);

protected string GetPassArgumentsDirect()
=> string.Join(
", ",
Expand Down Expand Up @@ -167,24 +186,69 @@ protected override SmartStringBuilder ReplaceCore(SmartStringBuilder smartString
return smartStringBuilder
.Replace("$CoreImpl$", coreImpl);
}
}

private string GetLoadArguments()
=> string.Join(
Environment.NewLine,
Descriptor.WorkloadMethod.GetParameters()
.Select((parameter, index) =>
// Used when Job.Run.ConsumeTasksSynchronously is enabled for (Value)Task(<T>)-returning workloads.
// Generates a synchronous loop that blocks on the returned task via AwaitHelper.GetResult, matching the
// pre-async-refactor behavior so historical results stay comparable.
internal sealed class SyncTaskDeclarationsProvider(BenchmarkCase benchmark) : DeclarationsProvider(benchmark)
{
public override string[] GetExtraFields() => [];

protected override SmartStringBuilder ReplaceCore(SmartStringBuilder smartStringBuilder)
{
string loadArguments = GetLoadArguments();
string passArguments = GetPassArguments();
string workloadMethodCall = $"global::{typeof(AwaitHelper).FullName}.{nameof(AwaitHelper.GetResult)}({GetWorkloadMethodCall(passArguments).TrimEnd(';')});";
string coreImpl = $$"""
private {{CoreReturnType}} OverheadActionUnroll({{CoreParameters}})
{
var refModifier = parameter.ParameterType.IsByRef ? "ref" : string.Empty;
return $"{refModifier} {parameter.ParameterType.GetCorrectCSharpTypeName()} arg{index} = {refModifier} this.__fieldsContainer.argField{index};";
})
);
{{loadArguments}}
{{StartClockSyncCode}}
while (--invokeCount >= 0)
{
this.__Overhead({{passArguments}});@Unroll@
}
{{ReturnSyncCode}}
}

private string GetPassArguments()
=> string.Join(
", ",
Descriptor.WorkloadMethod.GetParameters()
.Select((parameter, index) => $"{CodeGenerator.GetParameterModifier(parameter)} arg{index}")
);
private {{CoreReturnType}} OverheadActionNoUnroll({{CoreParameters}})
{
{{loadArguments}}
{{StartClockSyncCode}}
while (--invokeCount >= 0)
{
this.__Overhead({{passArguments}});
}
{{ReturnSyncCode}}
}

private {{CoreReturnType}} WorkloadActionUnroll({{CoreParameters}})
{
{{loadArguments}}
{{StartClockSyncCode}}
while (--invokeCount >= 0)
{
{{workloadMethodCall}}@Unroll@
}
{{ReturnSyncCode}}
}

private {{CoreReturnType}} WorkloadActionNoUnroll({{CoreParameters}})
{
{{loadArguments}}
{{StartClockSyncCode}}
while (--invokeCount >= 0)
{
{{workloadMethodCall}}
}
{{ReturnSyncCode}}
}
""";

return smartStringBuilder
.Replace("$CoreImpl$", coreImpl);
}
}

internal abstract class AsyncDeclarationsProviderBase(BenchmarkCase benchmark) : DeclarationsProvider(benchmark)
Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ public bool UseDisassemblyDiagnoser
[Option("evaluateOverhead", Required = false, HelpText = "Specifies whether to run and evaluate overhead iterations.")]
public bool? EvaluateOverhead { get; set; }

[Option("consumeTasksSynchronously", Required = false, Default = false, HelpText = "Specifies whether to consume (Value)Task-returning benchmarks synchronously.")]
public bool ConsumeTasksSynchronously { get; set; }

[Option("resume", Required = false, Default = false, HelpText = "Continue the execution if the last run was stopped.")]
public bool Resume { get; set; }

Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,8 @@ private static Job GetBaseJob(CommandLineOptions options, IConfig? globalConfig)
baseJob = baseJob.WithGcForce(false);
if (options.EvaluateOverhead is bool evaluateOverhead)
baseJob = baseJob.WithEvaluateOverhead(evaluateOverhead);
if (options.ConsumeTasksSynchronously)
baseJob = baseJob.WithConsumeTasksSynchronously(true);

if (options.EnvironmentVariables.Any())
{
Expand Down
5 changes: 3 additions & 2 deletions src/BenchmarkDotNet/Diagnosers/CompositeDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using BenchmarkDotNet.Attributes.CompilerServices;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
Expand Down Expand Up @@ -36,7 +37,7 @@ public async ValueTask HandleAsync(HostSignal signal, DiagnoserActionParameters
{
foreach (var diagnoser in diagnosers)
{
await diagnoser.HandleAsync(signal, parameters, cancellationToken).ConfigureAwait(true);
await diagnoser.HandleAsync(signal, parameters, cancellationToken).ConfigureAwait();
}
}

Expand Down Expand Up @@ -84,7 +85,7 @@ public async ValueTask HandleAsync(BenchmarkSignal signal, CancellationToken can
{
if (router.ShouldHandle(runMode))
{
await router.handler.HandleAsync(signal, parameters, cancellationToken).ConfigureAwait(true);
await router.handler.HandleAsync(signal, parameters, cancellationToken).ConfigureAwait();
}
}

Expand Down
Loading