4

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];
}
4

2 に答える 2