Skip to content

Improve Benchmark Accuracy #2336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
38e8cf8
Call benchmark method directly instead of via delegate.
timcassell Jun 19, 2023
c963dc9
Removed `Consumer` from benchmark actions.
timcassell Jun 21, 2023
562eb3c
Removed workload return types from code gen.
timcassell Jan 11, 2025
97d85df
Apply `NoOptimization` instead of `AggressiveOptimization` to loop me…
timcassell Jan 12, 2025
4b17ba1
Added assembly weaver.
timcassell Feb 13, 2025
1f65946
Remove unused constant.
timcassell Feb 13, 2025
9b51937
Remove InProcess check from validation.
timcassell Feb 27, 2025
256e91e
Fix comment.
timcassell Mar 4, 2025
d1e431c
Zero threshold.
timcassell Mar 4, 2025
7d42bb0
Update built package version.
timcassell May 29, 2025
e58edca
Fix test.
timcassell May 30, 2025
778648d
Use AsmResolver instead of Mono.Cecil.
timcassell Jun 6, 2025
e64c285
Improve pack-weaver task to auto-delete old package.
timcassell Jun 6, 2025
ecf85c4
Try latest beta version of AsmResolver.DotNet.
timcassell Jun 6, 2025
ed496c9
Fix removal of AggressiveInlining.
timcassell Jun 6, 2025
5e81b8e
Make AsmResolver non-transitive.
timcassell Jun 6, 2025
4521a7c
Change target back to AfterBuild.
timcassell Jun 6, 2025
2b813dc
Make all packages non-transitive.
timcassell Jun 6, 2025
c431200
Update packages to latest after rebase.
timcassell Jun 6, 2025
aea66e5
Don't write module if it's unnecessary.
timcassell Jun 6, 2025
c9acbc2
Rebuild after rebase.
timcassell Jun 9, 2025
238b6b6
Added comment.
timcassell Jun 10, 2025
b939dd7
Write to null stream before write to actual file.
timcassell Jun 10, 2025
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
7 changes: 7 additions & 0 deletions BenchmarkDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.P
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.Plotting.Tests", "tests\BenchmarkDotNet.Exporters.Plotting.Tests\BenchmarkDotNet.Exporters.Plotting.Tests.csproj", "{199AC83E-30BD-40CD-87CE-0C838AC0320D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Weaver", "src\BenchmarkDotNet.Weaver\BenchmarkDotNet.Weaver.csproj", "{5731DE42-16FE-430E-BA90-0EBE714CB221}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -161,6 +163,10 @@ Global
{199AC83E-30BD-40CD-87CE-0C838AC0320D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.Build.0 = Release|Any CPU
{5731DE42-16FE-430E-BA90-0EBE714CB221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5731DE42-16FE-430E-BA90-0EBE714CB221}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5731DE42-16FE-430E-BA90-0EBE714CB221}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5731DE42-16FE-430E-BA90-0EBE714CB221}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -190,6 +196,7 @@ Global
{2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
{B92ECCEF-7C27-4012-9E19-679F3C40A6A6} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
{199AC83E-30BD-40CD-87CE-0C838AC0320D} = {14195214-591A-45B7-851A-19D3BA2413F9}
{5731DE42-16FE-430E-BA90-0EBE714CB221} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F}
Expand Down
4 changes: 3 additions & 1 deletion NuGet.Config
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
<clear />

<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- reuquired to run Mono AOT benchmarks -->
<!-- required to run Mono AOT benchmarks -->
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
<add key="dotnet7" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json" />
<add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />
<add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
<add key="dotnet10" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10/nuget/v3/index.json" />

<add key="benchmarkdotnet.weaver" value="src/BenchmarkDotNet.Weaver/packages" />
</packageSources>
</configuration>
21 changes: 21 additions & 0 deletions build/BenchmarkDotNet.Build/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,29 @@ public static int Main(string[] args)
}
}

[TaskName(Name)]
[TaskDescription("Pack Weaver")]
public class PackWeaverTask : FrostingTask<BuildContext>, IHelpProvider
{
private const string Name = "pack-weaver";

public override void Run(BuildContext context) => context.BuildRunner.PackWeaver();

public HelpInfo GetHelp()
{
return new HelpInfo
{
Examples = new[]
{
new Example(Name)
}
};
}
}

[TaskName(Name)]
[TaskDescription("Restore NuGet packages")]
[IsDependentOn(typeof(PackWeaverTask))]
public class RestoreTask : FrostingTask<BuildContext>, IHelpProvider
{
private const string Name = "restore";
Expand Down
39 changes: 38 additions & 1 deletion build/BenchmarkDotNet.Build/Runners/BuildRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Cake.Common.Tools.DotNet.Workload.Install;
using Cake.Core;
using Cake.Core.IO;
using System.IO;
using System.Linq;

namespace BenchmarkDotNet.Build.Runners;

Expand All @@ -20,6 +22,41 @@ public BuildRunner(BuildContext context)
this.context = context;
}

public void PackWeaver()
{
var weaverPath = context.AllPackableSrcProjects.Single(p => p.GetFilename() == "BenchmarkDotNet.Weaver.csproj");
var outputPackageDir = weaverPath.GetDirectory().Combine("packages");

// Delete old package.
foreach (var file in Directory.EnumerateFiles(outputPackageDir.FullPath))
{
File.Delete(file);
}

context.DotNetRestore(weaverPath.GetDirectory().FullPath,
new DotNetRestoreSettings
{
MSBuildSettings = context.MsBuildSettingsRestore
});

context.Information("BuildSystemProvider: " + context.BuildSystem().Provider);
context.DotNetBuild(weaverPath.FullPath, new DotNetBuildSettings
{
NoRestore = true,
DiagnosticOutput = true,
MSBuildSettings = context.MsBuildSettingsBuild,
Configuration = context.BuildConfiguration,
Verbosity = context.BuildVerbosity
});

context.DotNetPack(weaverPath.FullPath, new DotNetPackSettings
{
OutputDirectory = outputPackageDir,
MSBuildSettings = context.MsBuildSettingsPack,
Configuration = context.BuildConfiguration
});
}

public void Restore()
{
context.DotNetRestore(context.SolutionFile.FullPath,
Expand Down Expand Up @@ -71,7 +108,7 @@ public void Pack()
var settingsSrc = new DotNetPackSettings
{
OutputDirectory = context.ArtifactsDirectory,
ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg"),
ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg").Append("-p:IsFullPack=true"),
MSBuildSettings = context.MsBuildSettingsPack,
Configuration = context.BuildConfiguration
};
Expand Down
6 changes: 6 additions & 0 deletions build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
</PropertyGroup>

<PropertyGroup Condition=" '$(VersionPrefix)' == '' ">
<!-- When this is changed, optionally reset WeaverVersionSuffix, then run `build.cmd pack-weaver`. -->
<VersionPrefix>0.15.2</VersionPrefix>
</PropertyGroup>

Expand All @@ -57,6 +58,11 @@
<PackageVersion>$(Version)</PackageVersion>
</PropertyGroup>

<PropertyGroup>
<!-- Increment this when the BenchmarkDotNet.Weaver package needs to be re-packed. -->
<WeaverVersionSuffix>-1</WeaverVersionSuffix>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<!-- MSBuild was complaing about InformationalVersion from common.props file, so I excluded them in conditional way -->
<IsFsharp>true</IsFsharp>
</PropertyGroup>
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net462;net8.0</TargetFrameworks>
Expand Down
34 changes: 34 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroSmokeStringBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using BenchmarkDotNet.Attributes;
using System.Text;

namespace BenchmarkDotNet.Samples
{
[MemoryDiagnoser(false)]
public class IntroSmokeStringBuilder
{
[Benchmark]
[Arguments(1)]
[Arguments(1_000)]
public StringBuilder Append_Strings(int repeat)
{
StringBuilder builder = new StringBuilder();

// strings are not sorted by length to mimic real input
for (int i = 0; i < repeat; i++)
{
builder.Append("12345");
builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN");
builder.Append("1234567890abcdefghijklmnopqrstuvwxy");
builder.Append("1234567890");
builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHI");
builder.Append("1234567890abcde");
builder.Append("1234567890abcdefghijklmnopqrstuvwxyzABCD");
builder.Append("1234567890abcdefghijklmnopqrst");
builder.Append("1234567890abcdefghij");
builder.Append("1234567890abcdefghijklmno");
}

return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<AssemblyTitle>Basic BenchmarkDotNet attributes that can be used to annotate your benchmarks</AssemblyTitle>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<NoWarn>$(NoWarn);1701;1702;1705;1591;3005;NU1702;CA1825</NoWarn>
<NoWarn>$(NoWarn);1701;1702;1705;1591;3005;NU1702;NU5100;CA1825</NoWarn>
<AssemblyName>BenchmarkDotNet.Annotations</AssemblyName>
<PackageId>BenchmarkDotNet.Annotations</PackageId>
<RootNamespace>BenchmarkDotNet</RootNamespace>
Expand All @@ -14,4 +14,20 @@
<ItemGroup>
<Compile Include="..\BenchmarkDotNet\Helpers\CodeAnnotations.cs" Link="Attributes\CodeAnnotations.cs" />
</ItemGroup>

<Choose>
<When Condition="'$(IsFullPack)' == 'true'">
<ItemGroup>
<!-- Include the BenchmarkDotNet.Weaver dlls in tasks without making it an explicit dependency. -->
<Content Include="$(MSBuildThisFileDirectory)..\BenchmarkDotNet.Weaver\bin\Release\$(TargetFramework)\**\*.dll" Pack="true" PackagePath="tasks/$(TargetFramework)" />
<Content Include="$(MSBuildThisFileDirectory)buildTransitive\**\*.targets" Pack="true" PackagePath="buildTransitive" />
</ItemGroup>
</When>
<Otherwise>
<!-- PackageReference for transitive weaver dependency for ProjectReferences to this. -->
<ItemGroup>
<PackageReference Include="BenchmarkDotNet.Weaver" Version="$(Version)$(WeaverVersionSuffix)" />
</ItemGroup>
</Otherwise>
</Choose>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Reference Include="BenchmarkDotNet.Annotations">
<HintPath>$(MSBuildThisFileDirectory)..\..\lib\netstandard2.0\BenchmarkDotNet.Annotations.dll</HintPath>
</Reference>
</ItemGroup>

<UsingTask TaskName="BenchmarkDotNet.Weaver.WeaveAssemblyTask" AssemblyFile="$(MSBuildThisFileDirectory)..\..\tasks\netstandard2.0\BenchmarkDotNet.Weaver.dll" />

<Target Name="WeaveAssemblies" AfterTargets="AfterBuild" >
<WeaveAssemblyTask TargetAssembly="$(TargetDir)$(TargetFileName)" />
</Target>
</Project>
1 change: 1 addition & 0 deletions src/BenchmarkDotNet.Weaver/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!packages/
34 changes: 34 additions & 0 deletions src/BenchmarkDotNet.Weaver/BenchmarkDotNet.Weaver.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
If any changes are made to this project, increment the WeaverVersionSuffix in the common.props file,
then run `build.cmd pack-weaver`.
-->

<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<VersionSuffix Condition="'$(IsFullPack)' != 'true'">$(VersionSuffix)$(WeaverVersionSuffix)</VersionSuffix>
<OutputPath>$(MSBuildThisFileDirectory)bin\$(Configuration)</OutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoWarn>$(NoWarn);NU5100;NU5128</NoWarn>
<!-- AsmResolver is not signed. -->
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<!-- Fix error NU5017: Cannot create a package that has no dependencies nor content. -->
<IsPackable Condition="'$(IsFullPack)' == 'true'">false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AsmResolver.DotNet" Version="6.0.0-beta.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.14.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.14.8" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<!-- Include .targets file and all DLLs in the output directory in the NuGet package -->
<Content Include="$(MSBuildThisFileDirectory)buildTransitive\**\*.targets" Pack="true" PackagePath="buildTransitive" />
<Content Include="$(OutputPath)**\*.dll" Pack="true" PackagePath="tasks/$(TargetFramework)" />
<None Remove="packages\**" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="BenchmarkDotNet.Weaver.WeaveAssemblyTask" AssemblyFile="$(MSBuildThisFileDirectory)..\..\tasks\netstandard2.0\BenchmarkDotNet.Weaver.dll" />

<Target Name="WeaveAssemblies" AfterTargets="AfterBuild" >
<WeaveAssemblyTask TargetAssembly="$(TargetDir)$(TargetFileName)" />
</Target>
</Project>
Binary file not shown.
92 changes: 92 additions & 0 deletions src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// *****
// If any changes are made to this file, increment the WeaverVersionSuffix in the common.props file,
// then run `build.cmd pack-weaver`.
// *****

using AsmResolver.DotNet;
using AsmResolver.PE.DotNet.Metadata.Tables;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.IO;
using System.Linq;

namespace BenchmarkDotNet.Weaver;

/// <summary>
/// The Task used by MSBuild to weave the assembly.
/// </summary>
public sealed class WeaveAssemblyTask : Task
{
/// <summary>
/// The path of the target assembly.
/// </summary>
[Required]
public string TargetAssembly { get; set; }

/// <summary>
/// Runs the weave assembly task.
/// </summary>
/// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns>
public override bool Execute()
{
if (!File.Exists(TargetAssembly))
{
Log.LogError($"Assembly not found: {TargetAssembly}");
return false;
}


bool benchmarkMethodsImplAdjusted = false;
try
{
var module = ModuleDefinition.FromFile(TargetAssembly);

foreach (var type in module.GetAllTypes())
{
// We can skip non-public types as they are not valid for benchmarks.
if (type.IsNotPublic)
{
continue;
}

foreach (var method in type.Methods)
{
if (method.CustomAttributes.Any(IsBenchmarkAttribute))
{
var oldImpl = method.ImplAttributes;
// Remove AggressiveInlining and add NoInlining.
method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
}
}
}

if (benchmarkMethodsImplAdjusted)
{
// Write to a null stream before overwriting the original file in case an exception occurs during the write (like unsupported platform).
// https://github.com/Washi1337/AsmResolver/issues/640
module.Write(Stream.Null);
module.Write(TargetAssembly);
}
}
catch (Exception e)
{
Log.LogWarning($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}. Error:{Environment.NewLine}{e}");
}
return true;
}

private static bool IsBenchmarkAttribute(CustomAttribute attribute)
{
// BenchmarkAttribute is unsealed, so we need to walk its hierarchy.
for (var attr = attribute.Constructor.DeclaringType; attr != null; attr = attr.Resolve()?.BaseType)
{
if (attr.FullName == "BenchmarkDotNet.Attributes.BenchmarkAttribute")
{
return true;
}
}
return false;
}
}
Loading