从内存,基准测试。NET将使用一些内部向导为所有框架运行基准测试。因此,与其使用现有的
preprocessor symbols
最好将测试分为两个不同的类
RuntimeMoniker
属性。例如:
[SimpleJob(RuntimeMoniker.Net48)]
public class UnderTestNet48
{
// Benchmarks
}
[SimpleJob(RuntimeMoniker.Net60)]
public class UnderTestNet60
{
// Benchmarks
}
现在,您需要修改运行基准测试的代码,因为它们是跨类拆分的,类似这样的东西可以工作:
public static void Main()
{
var config = DefaultConfig.Instance.
.WithOptions(ConfigOptions.JoinSummary)
.WithOptions(ConfigOptions.DisableLogFile);
BenchmarkRunner.Run(typeof(Program).Assembly, config);
}
[编辑自OP(Matthew Watson)]
多亏了这个答案,我才得以实现。
通过将常用的测试方法放入一个受保护的基类中,然后提供两个派生类,一个用于
net48
基准和一个
net5.0
基准
这是我最后得到的代码:
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
namespace Benchmark;
public static class Program
{
public static void Main()
{
BenchmarkRunner.Run(
typeof(Program).Assembly,
DefaultConfig.Instance
.WithOptions(ConfigOptions.JoinSummary)
.WithOptions(ConfigOptions.DisableLogFile));
}
}
public abstract class UnderTestBase
{
protected static Stream CreateStream()
{
return new MemoryStream(); // Or Stream.Null
}
protected void WriteUsingBitConverter(Stream output, double[] array)
{
foreach (var sample in array)
{
output.Write(BitConverter.GetBytes(sample), 0, sizeof(double));
}
}
protected 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
protected void WriteUsingSpan(Stream output, double[] array)
{
var span = array.AsSpan();
var bytes = MemoryMarshal.AsBytes(span);
output.Write(bytes);
}
#endif // NET6_0_OR_GREATER
protected readonly double[] Array = new double[100_000];
}
[SimpleJob(RuntimeMoniker.Net48)]
public class UnderTestNet48: UnderTestBase
{
[Benchmark]
public void UsingBitConverter()
{
using var stream = CreateStream();
WriteUsingBitConverter(stream, Array);
}
[Benchmark]
public void UsingMarshal()
{
using var stream = CreateStream();
WriteUsingMarshal(stream, Array);
}
}
[SimpleJob(RuntimeMoniker.Net60)]
public class UnderTestNet60: UnderTestBase
{
[Benchmark]
public void UsingBitConverter()
{
using var stream = CreateStream();
WriteUsingBitConverter(stream, Array);
}
[Benchmark]
public void UsingMarshal()
{
using var stream = CreateStream();
WriteUsingMarshal(stream, Array);
}
#if NET6_0_OR_GREATER
[Benchmark]
public void UsingSpan()
{
using var stream = CreateStream();
WriteUsingSpan(stream, Array);
}
#endif // NET6_0_OR_GREATER
}
这导致了这个输出:
| Type | Method | Job | Runtime | Mean | Error | StdDev |
|--------------- |------------------ |------------------- |------------------- |-----------:|----------:|----------:|
| UnderTestNet60 | UsingBitConverter | .NET 6.0 | .NET 6.0 | 4,110.8 us | 81.53 us | 151.13 us |
| UnderTestNet60 | UsingMarshal | .NET 6.0 | .NET 6.0 | 5,774.0 us | 114.78 us | 194.90 us |
| UnderTestNet60 | UsingSpan | .NET 6.0 | .NET 6.0 | 521.6 us | 5.13 us | 4.80 us |
| UnderTestNet48 | UsingBitConverter | .NET Framework 4.8 | .NET Framework 4.8 | 2,987.2 us | 35.60 us | 29.73 us |
| UnderTestNet48 | UsingMarshal | .NET Framework 4.8 | .NET Framework 4.8 | 5,616.9 us | 57.85 us | 48.30 us |
(顺便说一句,一个有趣的结果是
UsingBitConverter()
方法实际上似乎运行得更快
net48
与…相比
net6.0
-虽然这与
Span<T>
.)
[/编辑自OP(马修·沃森)]