本文告诉大家如何在项目使用性能测试测试自己写的方法



C# 标准性能测试 已经告诉大家如何使用 BenchmarkDotNet 测试性能,本文会告诉大家高级的用法。

建议是创建一个控制台项目用来做性能测试,这个项目要求是 dotnet framework 4.6 以上,建议是 4.7 的版本。使用这个项目引用需要测试的项目,然后在里面写测试的代码。

例如被测试项目有一个类 Foo 里面有一个方法是 lindexidb ,需要测试 林德熙逗比 方法的性能

最简单的测试的代码

public class FooPerf
{
[Benchmark]
public void lindexidb()
{
new Foo().lindexidb();
}
}

在 Main 函数使用下面代码

  var boKar = BenchmarkRunner.Run<Foo>();

这样就可以进行测试,如果需要传入一些参数,那么就需要使用本文的方法

传入参数

如果需要测试的方法需要传入不同的参数,而且在使用不同的参数的性能也是不相同,就需要使用传入参数特性。

例如有底层的项目

    public class Foo
{
public void Lindexidb()
{ }
}

需要创建另一个项目测试这个项目的性能, 需要注意不要在自己的库安装 BenchmarkDotNet ,安装之后会让启动速度慢很多

在测试性能的另一个项目,安装 BenchmarkDotNet 引用库测试,所有的代码

   class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<FooPerf>();
Console.Read();
}
} public class FooPerf
{
[Benchmark]
public void Lindexidb()
{
var foo = new Foo();
foo.Lindexidb();
}
}

需要知道,必须设置 FooPerf 的访问是 public 没有设置会出现异常

现在例如修改了 Lindexidb 需要传入参数

    public class Foo
{
public void Lindexidb(int a, int b)
{
var foo = a + b;
if (Arguments>a)
{
return;
}
if (foo == 2)
{
Arguments = foo;
}
} public int Arguments { get; set; }
}

现在需要修改性能项目

    public class FooPerf
{
[Benchmark(Description = "这里可以写这个方法是什么")]
public void Lindexidb()
{
var foo = new Foo();
foo.Lindexidb(2, 3);
}
}

可以看到上面写法很难写出测试很多参数

    public class FooPerf
{
[Benchmark]
[Arguments(100, 10)]
[Arguments(2, 10)]
[Arguments(2, 3)]
[Arguments(10, 3)]
[Arguments(21, 3)]
public void Lindexidb(int a,int b)
{
var foo = new Foo();
foo.Lindexidb(a, b);
}
}

通过 Arguments 可以传入不同的参数,使用这个方法可以防止初始化参数需要的时间计算为算法的

运行程序可以看到下面输出

//   FooPerf.Lindexidb: DefaultJob [a=2, b=3]
// FooPerf.Lindexidb: DefaultJob [a=2, b=10]
// FooPerf.Lindexidb: DefaultJob [a=10, b=3]
// FooPerf.Lindexidb: DefaultJob [a=21, b=3]
// FooPerf.Lindexidb: DefaultJob [a=100, b=10]

在使用不同的参数可以看到不同的速度

Method a b Mean Error StdDev
Lindexidb 2 3 2.037 ns 0.0749 ns 0.0833 ns
Lindexidb 2 10 3.263 ns 0.0992 ns 0.2682 ns
Lindexidb 10 3 2.333 ns 0.0798 ns 0.1038 ns
Lindexidb 21 3 2.278 ns 0.0776 ns 0.0863 ns
Lindexidb 100 10 2.364 ns 0.0809 ns 0.2242 ns

可以传入不同的参数,传入的参数可以自动转换

如果传入的参数不对,就会提示,如下面代码

        [Benchmark]
[Arguments("123", "123")]
[Arguments(2, 10)]
[Arguments(2, 3)]
[Arguments(10, 3)]
[Arguments(21, 3)]
public void Lindexidb(int a,int b)
{
var foo = new Foo();
foo.Lindexidb(a, b);
}

本来是使用 int 但是参数写 string 所以会出现下面提示

// Build Exception: The build has failed!
CS0029: Cannot implicitly convert type 'string' to 'int'
CS0029: Cannot implicitly convert type 'string' to 'int'

如果需要参数是 一个,如代码,传入的参数是两个,那么会出现异常

    public class FooPerf
{
[Benchmark]
[Arguments(1, 2)]
[Arguments(2, 10)]
[Arguments(2, 3)]
[Arguments(10, 3)]
[Arguments(21, 3)]
public void Lindexidb(int a)
{
var foo = new Foo();
var b = Arguments;
foo.Lindexidb(a, b);
} public int Arguments { get; set; }
}

属性

属性和字段都可以修改,但是修改字段需要修改公开字段,不推荐修改字段

        [Params(10, 2, 3)]
public int Arguments { get; set; }

可以设置属性的值为 10,2,3 在下面代码会组合属性和传入参数

        [Benchmark(Description = "这里可以写这个方法是什么")]
[Arguments(1)]
[Arguments(2)]
[Arguments(2)]
[Arguments(10)]
[Arguments(21)]
public void Lindexidb(int a)
{
var foo = new Foo();
var b = Arguments;
foo.Lindexidb(a, b);
} [Params(10, 2, 3)]
public int Arguments { get; set; }

运行看到有 15 个测试

//   FooPerf.Lindexidb: DefaultJob [Arguments=2, a=1]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=10]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=21]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=1]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=10]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=21]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=1]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=10]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=21]

传入多个值

可以看到在特性写参数是比较多的,如果需要很多参数就需要写很多代码

可以使用数组的方式把很多的代码作为数组

请看代码

        [Benchmark(Description = "这里可以写这个方法是什么")]
[ArgumentsSource(nameof(LeesikeasowSearjeeball))]
public void Lindexidb(int a, int b)
{
var foo = new Foo();
foo.Lindexidb(a, b);
} public IEnumerable<object[]> LeesikeasowSearjeeball()
{
yield return new object[] {2, 3};
yield return new object[] {10, 2};
yield return new object[] {5, 2};
yield return new object[] {100, 5};
yield return new object[] {3, 100};
}

上面使用 LeesikeasowSearjeeball 作为输入的参数,注意需要返回一个数组,这个数组里就是参数的列表。上面使用的参数有两个,所以数组就是包含两个参数

//   FooPerf.Lindexidb: DefaultJob [Arguments=2, a=2, b=3]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=3, b=100]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=5, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=10, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=2, a=100, b=5]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=2, b=3]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=3, b=100]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=5, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=10, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=3, a=100, b=5]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=2, b=3]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=3, b=100]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=5, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=10, b=2]
// FooPerf.Lindexidb: DefaultJob [Arguments=10, a=100, b=5]

除了可以设置方法传入,还可以设置属性

        [Benchmark]
public void Foo()
{
for (int i = 0; i < Arguments; i++)
{ }
} [ParamsSource(nameof(PememasiDismikasu))]
public int Arguments { get; set; } public IEnumerable<int> PememasiDismikasu => new[] { 100, 200 };

通过 ParamsSource 可以告诉测试使用的从哪个拿到

基线

基线可以用在三个不同的地方,最简单的是方法,另外可以用在分类和不同环境。

因为测试的时间在不同的设备的时间都不相同,如何判断一个方法优化之后是比原来好?方法就是把原来的方法作为基线,这样可以对比不同的方法的速度

如有三个不同的方法,选一个作为基线

        [Benchmark]
public void Time50() => Thread.Sleep(50); [Benchmark(Baseline = true)]
public void Time100() => Thread.Sleep(100); [Benchmark]
public void Time150() => Thread.Sleep(150);

设置基线的方法是添加 Baseline = true ,建议在原来的方法添加,然后使用不同的方法看哪个方法的速度比较快

在输出会添加一列 Scaled 用于表示这个方法对比基线的速度,他的时间是基线的多少。如上面代码的运行会输出

Method Mean Error StdDev Scaled
Time50 50.46 ms 0.0779 ms 0.0729 ms 0.50
Time100 100.39 ms 0.0762 ms 0.0713 ms 1.00
Time150 150.48 ms 0.0986 ms 0.0922 ms 1.50

这里的 Scaled 就是对比基线方法的时间

如果在不同的分类下需要做不同的标准,就可以在 BenchmarkCategory 添加 Baseline 告诉在哪个分类使用哪个方法作为标准。如下面的代码,设置 Fast 类和 Slow 类使用不同的标准

    [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class IntroCategoryBaseline
{
[BenchmarkCategory("Fast"), Benchmark(Baseline = true)]
public void Time50() => Thread.Sleep(50); [BenchmarkCategory("Fast"), Benchmark]
public void Time100() => Thread.Sleep(100); [BenchmarkCategory("Slow"), Benchmark(Baseline = true)]
public void Time550() => Thread.Sleep(550); [BenchmarkCategory("Slow"), Benchmark]
public void Time600() => Thread.Sleep(600);
}

运行的输出,可以看到对于不同的分类用的是不同的方法

Method Categories Mean Error StdDev Scaled
Time50 Fast 50.46 ms 0.0745 ms 0.0697 ms 1.00
Time100 Fast 100.47 ms 0.0955 ms 0.0893 ms 1.99
Time550 Slow 550.48 ms 0.0525 ms 0.0492 ms 1.00
Time600 Slow 600.45 ms 0.0396 ms 0.0331 ms 1.09

基线除了可以测试方法的基线,还可以测试环境。如我的代码需要在 Clr Mono Core 三个不同环境运行,这时我想知道对比 Clr 环境,其他两个环境的性能。可以使用 JobBaseline 的方式。

    [ClrJob(baseline: true)]
[MonoJob]
[CoreJob]
public class IntroJobBaseline
{
[Benchmark]
public int SplitJoin()
=> string.Join(",", new string[1000]).Split(',').Length;
}

这时输出可以看到 Clr 运行的是标准,在 Core 运行时间是在 Clr 运行的 0.67 通过这个方法就知道在不同的环境相同的方法的测试

Method Runtime Mean Error StdDev Scaled ScaledSD
SplitJoin Clr 19.42 us 0.2447 us 0.1910 us 1.00 0.00
SplitJoin Core 13.00 us 0.2183 us 0.1935 us 0.67 0.01
SplitJoin Mono 39.14 us 0.7763 us 1.3596 us 2.02 0.07

更多关于基线请看 Benchmark and Job Baselines

分类

如果在一个类的测试方法有不同的类型,而只需要测试某几个类型的就需要使用本文的方法

    [DryJob]
[CategoriesColumn]
[BenchmarkCategory("分类")]
[AnyCategoriesFilter("A", "1")]
public class FooPerf
{
[Benchmark]
[BenchmarkCategory("A", "1")]
public void A1() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
[BenchmarkCategory("A", "2")]
public void A2() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
[BenchmarkCategory("B", "1")]
public void B1() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
[BenchmarkCategory("B", "2")]
public void B2() => Thread.Sleep(10);
}

在方法表示方法属于的类型,可以标记一个方法属于多个类型,如 A1 方法属于 A 1 两个类型,在类标记 AnyCategoriesFilter 表示测试某些类型,这里标记了 A 和 1 也就是所有包含 A 或 1 类型的方法会被测试,所以 A1 A2 B1 都会被运行。

包含名字

如果在一个类有很多方法,只需要名字满足某些条件的方法才可以执行,就需要进行包含名字判断。

    [Config(typeof(Config))]
public class FooPerf
{
private class Config : ManualConfig
{
// 只有在名字满足包含 "A" 或 "1" 和名字长度小于 3 才执行
public Config()
{
Add(new DisjunctionFilter
(
// 这里的是或关系
// 只要名字包含 "A" 或 "1" 就执行
new NameFilter(name => name.Contains("A")),
new NameFilter(name => name.Contains("1"))
)); // 这里和上面是 And 关系,也就是必须要同时满足名字长度小于 3 才可以执行
Add(new NameFilter(name => name.Length < 3));
}
} [Benchmark]
public void A1() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
public void A2() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
public void A3() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
public void B1() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
public void B2() => Thread.Sleep(10); [Benchmark]
public void B3() => Thread.Sleep(10); [Benchmark]
public void C1() => Thread.Sleep(10); // Will be benchmarked [Benchmark]
public void C2() => Thread.Sleep(10); [Benchmark]
public void C3() => Thread.Sleep(10); [Benchmark]
public void Aaa() => Thread.Sleep(10);
}

在类添加特性,告诉这个类需要使用哪个配置,在配置的构造函数使用了两次的 Add 函数,在多个 Add 之间是 And 关系,也就是必须所有 Add 的条件都满足才可以执行。在一个Add使用的 DisjunctionFilter 可以使用或关系多个条件。

上面的函数使用的满足名字带有 A 或 1 而且名字的长度小于 3 才可以执行。

除了使用名字作为条件,还可以使用 AnyCategoriesFilter 表示存在任意的类型符合,AllCategoriesFilter 要求所有的类型都符合。

运行多个类

一个需要测试的类需要使用下面代码

            BenchmarkRunner.Run<FooPerf>();

只能测试一个类,如果有很多类就需要写很多代码,下面告诉大家如何找到所有方法

 BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args);

在运行的时候就可以选运行哪个

我搭建了自己的博客 https://blog.lindexi.com/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新

如果在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎大家加入


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

C# 标准性能测试高级用法的更多相关文章

  1. 2019-11-29-C#-标准性能测试高级用法

    title author date CreateTime categories C# 标准性能测试高级用法 lindexi 2019-11-29 10:13:16 +0800 2018-07-08 0 ...

  2. 2019-8-31-C#-标准性能测试高级用法

    title author date CreateTime categories C# 标准性能测试高级用法 lindexi 2019-08-31 16:55:58 +0800 2018-07-08 0 ...

  3. C# 标准性能测试

    经常我写一个类,作为一个工具类,小伙伴会问我这个类的性能,这时我就需要一个标准的工具进行测试. 本文告诉大家如何使用 benchmarkdotnet 做测试. 现在在 github 提交代码,如果有小 ...

  4. Newtonsoft.Json高级用法(转)

    手机端应用讲究速度快,体验好.刚好手头上的一个项目服务端接口有性能问题,需要进行优化.在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数 ...

  5. 【转】 Newtonsoft.Json高级用法

    手机端应用讲究速度快,体验好.刚好手头上的一个项目服务端接口有性能问题,需要进行优化.在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数 ...

  6. Newtonsoft.Json高级用法 1.忽略某些属性 2.默认值的处理 3.空值的处理 4.支持非公共成员 5.日期处理 6.自定义序列化的字段名称

    手机端应用讲究速度快,体验好.刚好手头上的一个项目服务端接口有性能问题,需要进行优化.在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数 ...

  7. SQL[连载3]sql的一些高级用法

    SQL[连载3]sql的一些高级用法 SQL 高级教程 SQL SELECT TOP SQL SELECT TOP 子句 SELECT TOP 子句用于规定要返回的记录的数目. SELECT TOP ...

  8. nmap命令-----高级用法

    探测主机存活常用方式 (1)-sP :进行ping扫描 打印出对ping扫描做出响应的主机,不做进一步测试(如端口扫描或者操作系统探测):  下面去扫描10.0.3.0/24这个网段的的主机 nmap ...

  9. 细说 ASP.NET Cache 及其高级用法

    许多做过程序性能优化的人,或者关注过程程序性能的人,应该都使用过各类缓存技术. 而我今天所说的Cache是专指ASP.NET的Cache,我们可以使用HttpRuntime.Cache访问到的那个Ca ...

随机推荐

  1. 如何用好消息推送(push)做APP运营

    作为移动端APP产品运营最重要的运营手段,消息推送(push)被越来越多的APP厂商所重视,在信息泛滥的移动互联网时代,手机APP应用安装得越来越多,小小的手机屏幕每天收到的消息推送也越来越多,站在用 ...

  2. day39-Spring 08-Spring的AOP:基于AspectJ的注解

    基于AspectJ的注解的开发要重点掌握. 这些表达式肯定要应用在我们的某些个增强上. 学习AspectJ也是两种形式:一种是XML,一种是注解.AspectJ的增强,就是那些通知的类型.Aspect ...

  3. 2019.9.10附加题while练习

    题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%:20万到40万之 ...

  4. Python学习之路3☞编程风格

    语句和语法 #   表示注释掉的内容 \    续行 print("yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\ yyyyyyyyyyyyyyyyyyyyyyy& ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader) 代码工 ...

  6. 笔记: CC2540 和 CC2541 的区别

    CC2540 和 CC2541 的区别 CC2540 和 CC2541 是 BLE 低功耗蓝牙芯片,使用的是 51 内核. CC2540 有 USB 接口 CC2541 无. 价格上最早是 CC254 ...

  7. 洛谷 1463[SDOI2005] 反素数ant

    题目描述 对于任何正整数x,其约数的个数记作g(x).例如g(1)=1.g(6)=4. 如果某个正整数x满足:g(x)>g(i) 0<i<x,则称x为反质数.例如,整数1,2,4,6 ...

  8. 【转载】 C++ stl string 操作

        总结一下C++中string的操作,来自〈C++ Primer〉第四版. 1. string对象的定义和初始化: 12345678910111213 string s1; //空串string ...

  9. Code Force 429B Working out【递推dp】

    Summer is coming! It's time for Iahub and Iahubina to work out, as they both want to look hot at the ...

  10. tp3 key json 分页

    //json 强制转换为array $arr[$key]['checkpro'] = json_decode($val['checkpro'],JSON_FORCE_ARRAY);    $arr[$ ...