.Net 性能测试框架

介绍

以前看过一些文章通过IL优化反射等。。,但在实践上面用得比较少,刚好最近在研究反射的性能优化,需要测试对应的优化是否在性能上真实有效。 顺便附带了Net5.0与NetCore3.1中反射的基准测试结果。

BenchmarkDotNet 帮助你能够帮助你测量方法的性能,并支持基于特性(Attribute)或支持Fluent方法添加测试任务。 可以通过创建Console项目直接对方法进行测试,对代码没有侵入性(前提是类与类之前的耦合度要降低)。

快速入门

  1. 创建一个Console项目
  2. 安装 BenchmarkDotNet Install-Package BenchmarkDotNet
  3. 官方的基准测试用例
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);
        }
   }

参考链接

BenchmarkDotnet 官方指南

C# 标准性能测试 林德熙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值