介绍
以前看过一些文章通过IL优化反射等。。,但在实践上面用得比较少,刚好最近在研究反射的性能优化,需要测试对应的优化是否在性能上真实有效。 顺便附带了Net5.0与NetCore3.1中反射的基准测试结果。
BenchmarkDotNet 帮助你能够帮助你测量方法的性能,并支持基于特性(Attribute)或支持Fluent方法添加测试任务。 可以通过创建Console
项目直接对方法
进行测试,对代码没有侵入性(前提是类与类之前的耦合度要降低)。
快速入门
- 创建一个
Console
项目 - 安装 BenchmarkDotNet
Install-Package BenchmarkDotNet
- 官方的基准测试用例
using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace MyBenchmarks
{
public class Md5VsSha256
{
private const int N = 10000;
private readonly byte[] data;
private readonly SHA256 sha256 = SHA256.Create();
private readonly MD5 md5 = MD5.Create();
public Md5VsSha256()
{
data = new byte[N];
new Random(42).NextBytes(data);
}
[Benchmark]
public byte[] Sha256() => sha256.ComputeHash(data);
[Benchmark]
public byte[] Md5() => md5.ComputeHash(data);
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<Md5VsSha256>();
}
}
}
名词解释
OverheadJitting
提供Jit即时编译器初始化 测试方法空负载
WorkloadJitting
提供Jit即时编译器初始化 测试方法
WorkloadPilot
负载探测(按照1秒左右时间,测试方法在1秒内能够运行方法次数)
OverheadWarmup
空负载方法 预热
OverheadActual
空负载方法 实际测试
WorkloadWarmup
测试方法 预热
WorkloadActual
测试方法 实际测试
WorkloadResult
测试方法测量平均时间 - 空负载方法测试平均时间
Mean
: 全部测量结果的平均耗时 Arithmetic mean of all measurements
Error
: 测量结果的可信度区间 Half of 99.9% confidence interval
StdDev
: 全部测量结果的标准偏差 Standard deviation of all measurements
Median
: 全部测试结果的中间值 Value separating the higher half of all measurements (50th percentile)
Gen 0
: 每执行1000次方法 GC中0代使用的内存。
Allocated
: 每执行一次方法申请的内存。
1ms
= 1毫秒
= 0.001 秒
1us
= 1微秒
= 0.000001 秒
1ns
= 1纳秒
= 0.000000001 秒
例子:
# 测试方法 第15轮迭代真实测试, 执行方法次数33554432次, 耗时834926300.00ns, 平均每次调用耗费 24.8827ns
WorkloadActual 15: 33554432 op, 834926300.00 ns, 24.8827 ns/op
反射性能测试
废话不多说,先上图。
在这个例子为在Net5.0与NetCore3.1框架测试的结果。 由图可见 Net5在NetCore3.1的性能优化还是挺大的。
这里还有一个问题, 问题就是Directly Create Instance
我是通过在泛型方法中new T()
的时间比我用反射的耗时还长?
基于DynamicMethod IL与静态构造函数缓存反射
public class ILReflect<T> : IFastReflect<T> where T : class
{
public T CreateInstance()
{
return FastILReflect<T>.CreateInstance();
}
}
public static class FastILReflect<T>
{
private static Func<T> _createMethod;
static FastILReflect()
{
var constructor = typeof(T).GetConstructor(Array.Empty<Type>());
var newMethod = new DynamicMethod("CreateInstace", typeof(T), Array.Empty<Type>());
var ilGenerator = newMethod.GetILGenerator();
ilGenerator.Emit(OpCodes.Newobj, constructor);
ilGenerator.Emit(OpCodes.Ret);
_createMethod = (Func<T>)newMethod.CreateDelegate(typeof(Func<T>));
}
public static T CreateInstance()
{
//ILGenertor
return _createMethod();
}
}
由于此篇文章为性能测试,这个例子只是用来测试,这个反射例子还可以进一步的封装。
框架特性使用
多框架测试
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net50)] // 在Net5.0测试
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)] // 在NetCore3.1测试
public class DelegateReflectTests
{
[Benchmark(Description = "Directly Create Instance")]
[Fact]
public void CreateInstaceByDirectly()
{
TestType type = new DirectRealize<TestType>().CreateInstance();
Assert.NotNull(type);
}
}
注意: 这里工程的SDK配置需要添加.Net5与NetCore3.1框架编译
<TargetFrameworks>net5.0;netcoreapp3.1</TargetFrameworks>
参数化测试
public class IntroParams
{
[Params(100, 200)]
public int A { get; set; }
[Params(10, 20)]
public int B { get; set; }
[Benchmark]
public void Benchmark() => Thread.Sleep(A + B + 5);
}
输出结果:
| Method | A | B | Mean | Error | StdDev |
|---------- |---- |--- |---------:|--------:|--------:|
| Benchmark | 100 | 10 | 115.3 ms | 0.13 ms | 0.12 ms |
| Benchmark | 100 | 20 | 125.4 ms | 0.14 ms | 0.12 ms |
| Benchmark | 200 | 10 | 215.5 ms | 0.19 ms | 0.18 ms |
| Benchmark | 200 | 20 | 225.4 ms | 0.17 ms | 0.16 ms |
基准测试初始化
public class IntroSetupCleanupGlobal
{
[Params(10, 100, 1000)]
public int N;
private int[] data;
[GlobalSetup]
public void GlobalSetup()
{
data = new int[N]; // 根据每个参数化N 初始化一次
}
[Benchmark]
public int Logic()
{
int res = 0;
for (int i = 0; i < N; i++)
res += data[i];
return res;
}
[GlobalCleanup]
public void GlobalCleanup()
{
// Disposing logic
}
}
诊断(Diagnoser)
BenchmarkDotNet支持很多种诊断器。 其中包含但不限于,内存诊断器 MemoryDiagnoser
,事件探测器 EtWProfilter
, 线程诊断器 ThreadingDiagnoser
。。
通过在测试类添加特性即可。 例如添加内存诊断器
[MemoryDiagnoser]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net50)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
public class DelegateReflectTests
{
[Benchmark(Description = "Directly Create Instance")]
[Fact]
public void CreateInstaceByDirectly()
{
TestType type = new DirectRealize<TestType>().CreateInstance();
Assert.NotNull(type);
}
}