【发布时间】:2022-01-13 09:37:23
【问题描述】:
假设您正在编写一些与 BenchmarkDotNet 一起使用的基准测试,这些基准测试是多目标的 net48 和 net6.0,并且其中一个基准测试只能针对 net6.0 目标编译。
显而易见的做法是使用类似这样的方法从 net48 构建中排除特定基准:
#if NET6_0_OR_GREATER
[Benchmark]
public void UsingSpan()
{
using var stream = new MemoryStream();
writeUsingSpan(stream, _array);
}
static void writeUsingSpan(Stream output, double[] array)
{
var span = array.AsSpan();
var bytes = MemoryMarshal.AsBytes(span);
output.Write(bytes);
}
#endif // NET6_0_OR_GREATER
不幸的是,这不起作用,它不起作用的方式取决于项目文件中TargetFrameworks 属性中指定的目标的顺序。
如果您订购框架,使 net6.0 首先是 <TargetFrameworks>net6.0;net48</TargetFrameworks>,那么(在上面的示例中)UsingSpan() 方法包含在两个目标中,导致 net48 目标和输出的 BenchmarkDotNet 构建错误比如这样:
| Method | Job | Runtime | Mean | Error | StdDev |
|------------------ |------------------- |------------------- |-----------:|----------:|----------:|
| UsingBitConverter | .NET 6.0 | .NET 6.0 | 325.587 us | 2.0160 us | 1.8858 us |
| UsingMarshal | .NET 6.0 | .NET 6.0 | 505.784 us | 4.3719 us | 4.0894 us |
| UsingSpan | .NET 6.0 | .NET 6.0 | 4.942 us | 0.0543 us | 0.0482 us |
| UsingBitConverter | .NET Framework 4.8 | .NET Framework 4.8 | NA | NA | NA |
| UsingMarshal | .NET Framework 4.8 | .NET Framework 4.8 | NA | NA | NA |
| UsingSpan | .NET Framework 4.8 | .NET Framework 4.8 | NA | NA | NA |
另一方面,如果您对框架进行排序,以便 net48 首先是 <TargetFrameworks>net48;net6.0</TargetFrameworks>,那么(在上面的示例中)UsingSpan() 方法对于两个目标都排除,结果输出如下:
| Method | Job | Runtime | Mean | Error | StdDev |
|------------------ |------------------- |------------------- |---------:|---------:|---------:|
| UsingBitConverter | .NET 6.0 | .NET 6.0 | 343.1 us | 6.51 us | 11.57 us |
| UsingMarshal | .NET 6.0 | .NET 6.0 | 539.5 us | 10.77 us | 22.94 us |
| UsingBitConverter | .NET Framework 4.8 | .NET Framework 4.8 | 331.2 us | 5.43 us | 5.08 us |
| UsingMarshal | .NET Framework 4.8 | .NET Framework 4.8 | 588.9 us | 11.18 us | 10.98 us |
我必须通过单一目标项目和编辑项目文件以分别针对框架,然后为每个目标单独运行基准测试来解决这个问题。
有没有办法让这个项目与多目标项目一起工作?
为了完整起见,这里有一个完整的可编译测试应用程序来演示该问题。我正在使用 Visual Studio 2022。
项目文件:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
</ItemGroup>
“Program.cs”文件:
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
namespace Benchmark;
public static class Program
{
public static void Main()
{
BenchmarkRunner.Run<UnderTest>();
}
}
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.Net60)]
public class UnderTest
{
[Benchmark]
public void UsingBitConverter()
{
using var stream = new MemoryStream();
writeUsingBitConverter(stream, _array);
}
static void writeUsingBitConverter(Stream output, double[] array)
{
foreach (var sample in array)
{
output.Write(BitConverter.GetBytes(sample), 0, sizeof(double));
}
}
[Benchmark]
public void UsingMarshal()
{
using var stream = new MemoryStream();
writeUsingMarshal(stream, _array);
}
static void writeUsingMarshal(Stream output, double[] array)
{
const int SIZE_BYTES = sizeof(double);
byte[] buffer = new byte[SIZE_BYTES];
IntPtr ptr = Marshal.AllocHGlobal(SIZE_BYTES);
foreach (var sample in array)
{
Marshal.StructureToPtr(sample, ptr, true);
Marshal.Copy(ptr, buffer, 0, SIZE_BYTES);
output.Write(buffer, 0, SIZE_BYTES);
}
Marshal.FreeHGlobal(ptr);
}
#if NET6_0_OR_GREATER
[Benchmark]
public void UsingSpan()
{
using var stream = new MemoryStream();
writeUsingSpan(stream, _array);
}
static void writeUsingSpan(Stream output, double[] array)
{
var span = array.AsSpan();
var bytes = MemoryMarshal.AsBytes(span);
output.Write(bytes);
}
#endif // NET6_0_OR_GREATER
readonly double[] _array = new double[10_000];
}
【问题讨论】:
-
这看起来像是 BenchmarkDotNet 中的一个错误。我建议在项目的 github 中创建一个问题。
-
您可能还需要将
#if NET6_0_OR_GREATER放在[SimpleJob(RuntimeMoniker.Net60)]属性周围。 -
@DavidG 我会尝试一下并报告。
-
@DavidG 不幸的是,这并没有帮助 - 结果是一样的。
-
好的,下一个选项...创建两个不同的类,一个使用
RuntimeMoniker.Net60,一个使用RuntimeMoniker.Net48,然后使用BenchmarkRunner.Run(typeof(Program).Assembly, DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary).WithOptions(ConfigOptions.DisableLogFile));?
标签: c# multitargeting benchmarkdotnet