BenchmarkDotNet で使用する、net48
およびのマルチターゲットのベンチマークを作成net6.0
していて、それらのベンチマークの 1 つがターゲットに対してのみコンパイルできるとし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()
メソッドは両方のターゲットに含まれ、ターゲットの BenchmarkDotNet ビルド エラーが発生し、次のnet48
ように出力されます。
| 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];
}