2019-11-29-C#-标准性能测试高级用法
title | author | date | CreateTime | categories |
---|---|---|---|---|
C# 标准性能测试高级用法
|
lindexi
|
2019-11-29 10:13:16 +0800
|
2018-07-08 09:29:53 +0800
|
C#
|
本文告诉大家如何在项目使用性能测试测试自己写的方法
在 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);
在运行的时候就可以选运行哪个
2019-11-29-C#-标准性能测试高级用法的更多相关文章
- 2019-8-31-C#-标准性能测试高级用法
title author date CreateTime categories C# 标准性能测试高级用法 lindexi 2019-08-31 16:55:58 +0800 2018-07-08 0 ...
- C# 标准性能测试高级用法
本文告诉大家如何在项目使用性能测试测试自己写的方法 在 C# 标准性能测试 已经告诉大家如何使用 BenchmarkDotNet 测试性能,本文会告诉大家高级的用法. 建议是创建一个控制台项目用来做性 ...
- 2019.11.29 Mysql的数据操作
为名为name的表增加数据(插入所有字段) insert into name values(1,‘张三’,‘男’,20); 为名为name的表增加数据(插入部分字段) insert into name ...
- 2019.11.29 SAP SMTP郵件服務器配置 發送端 QQ郵箱
今天群裏的小夥伴問了如何配置郵件的問題,隨自己在sap裏面配置了一個 1. RZ10配置參數 a) 参数配置前,先导入激活版本 执行完毕后返回 b) 输入参数文件DEFAU ...
- pycharm+anaconda在Mac上的配置方法 2019.11.29
内心os: 听人说,写blog是加分项,那他就不是浪费时间的事儿了呗 毕竟自己菜还是留下来东西来自己欣赏吧 Mac小电脑上进行python数据开发环境的配置 首先下载Anaconda,一个超好用的数据 ...
- Supervision meeting notes 2019/11/29
topic 分支: 1. subgraph/subsequence mining Wang Jin, routine behavior/ motif. Philippe Fournier Viger ...
- C# 标准性能测试
经常我写一个类,作为一个工具类,小伙伴会问我这个类的性能,这时我就需要一个标准的工具进行测试. 本文告诉大家如何使用 benchmarkdotnet 做测试. 现在在 github 提交代码,如果有小 ...
- EOJ Monthly 2019.11 E. 数学题(莫比乌斯反演+杜教筛+拉格朗日插值)
传送门 题意: 统计\(k\)元组个数\((a_1,a_2,\cdots,a_n),1\leq a_i\leq n\)使得\(gcd(a_1,a_2,\cdots,a_k,n)=1\). 定义\(f( ...
- nmap命令-----高级用法
探测主机存活常用方式 (1)-sP :进行ping扫描 打印出对ping扫描做出响应的主机,不做进一步测试(如端口扫描或者操作系统探测): 下面去扫描10.0.3.0/24这个网段的的主机 nmap ...
随机推荐
- 190707Python-MySQL
一.Python连接MySQL import pymysql conn = pymysql.connect(host='192.168.100.4', port=3306, user='dongfei ...
- vscode 配置go环境 可调试
1.go下载安装并配置环境变量 2.安装工具: 1)安装gocode go get -u -v github.com/nsf/gocode 2)安装godef go get -u -v githu ...
- laravel中事件的监听和订阅
一.前言 更新员工部门主管的时候,需要重新更新一下缓存,这个会比较耗时.所以计划放到队列中来执行.后来想了想,其实用一下事件监听也能实现.人家都说好,然是我也没感觉到有什么好的. 二.正文 1. 在p ...
- 通过daemon.json配置docker 2375 API端口,配置私有http仓库
编辑daemon.json vi /etc/docker/daemon.json 配置如下内容即可 { "hosts":[ "tcp://0.0.0.0:2375&quo ...
- Android 的四大组件都需要在清单文件中注册吗?
Activity . Service . ContentProvider 如 果 要 使 用 则 必 须 在 AndroidManifest.xml 中 进 行 注 册 , 而BroadcastRec ...
- 趣谈linux操作系统笔记-从BIOS到bootloader
BIOS 在主板上,有一个东西叫ROM(Read Only Memory,只读存储器).这和咱们平常说的内存RAM(Read Access Memory,随机存取存储器)不同. 而 ROM 是只读的, ...
- nodejs之express路由与动态路由
1.快速创建express项目步骤 /** * 1.cd 到项目里面 * 2.npm init --yes 创建package.json文件 * 3.安装express * npm install e ...
- python之拷贝文件
做了个小实验, 用于拷贝文件夹下面的jpg. 用于拓展, 可以引入类和方法, 拷贝你指定的任意类型的文件. import os src = 'C:\\Users\\Administrator\\Des ...
- 调整Linux终端显示分辨率
linux 默认cli (command line interface)分辨率一般都比较小,显示的字体很大,不太美观,有时还影响结果的显示(例如出现kernel panic).所以有必要改变一下cli ...
- Selenium 2自动化测试实战19(下载文件)
一.下载文件 webDriver允许设置默认的文件下载路径,也就是说,文件会自动下载并且存放到设置的目录中.下面以火狐浏览器为例,执行文件的下载. #downfile.py # -*- coding: ...