欢迎光临散文网 会员登陆 & 注册

使用Crank给我们的类库做基准测试

2023-04-07 15:39 作者:Cpp程序员  | 我要投稿

什么是 Crank

Crank 是.NET团队用于运行基准测试的基础设施,包括(但不限于)TechEmpower Web Framework基准测试中的场景。 Crank 第一次出现在公众的视野应该是在 .NET Conf 2021, @sebastienros 演讲的 Benchmarking ASP.NET Applications with .NET Crank。

Crank 是 client-server (C/S) 的架构,主要有一个控制器 (Controller) 和一个或多个代理 (Agent) 组成。 其中控制器就是 client,负责发送指令;代理就是 server,负责执行 client 发送的指令,也就是执行具体的测试内容。

下面是它的架构图。

可以看到,控制器和代理之间的交互是通过 HTTP 请求来驱动的。然后代理可以执行多个不同类型的作业类型。

我们这篇博客主要讲的是图中的 .NET project Job

先来看看官方仓库一个比较简单的入门示例。

入门示例

首先要安装 crank 相关的两个工具,一个是控制器,一个是代理。

dotnet tool update Microsoft.Crank.Controller --version "0.2.0-*" --globaldotnet tool update Microsoft.Crank.Agent --version "0.2.0-*" --global

然后运行官方仓库上面的 micro 示例,是一个 Md5 和 SHA 256 对比的例子。

public class Md5VsSha256{    [Params(100, 500)]    public int N { get; set;}    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); }

要注意的是 Main 方法,要用 BenchmarkSwitcher 来运行,因为 Crank 是用命令行来执行的,会附加一些参数,也就是代码中的 args。

public static void Main(string[] args){    BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); }

然后是控制器要用到的配置文件,里面就是要执行的基准测试的内容,要告诉代理怎么执行。

# 作业jobs:  # 作业名,自定义  benchmarks:    # 源相关内容    source:      # 这里是本地文件夹,也可以配置远程 repository 和分支      localFolder: .      # 这个是具体的 csproj      project: micro.csproj    # 一些变量    variables:      filterArg: "*"      jobArg: short    # 参数    arguments: --job {{jobArg}} --filter {{filterArg}} --memory    options:      # 使用 BenchmarkDotNet      benchmarkDotNet: true# 场景    scenarios:  # 场景名,自定义  Md5VsSha256:    application:      # 与前面的定义作业名一致      job: benchmarks# 档案profiles:  # 档案名,自定义  local:    jobs:      application:        # 代理的地址        endpoints:          - http://localhost:5010

下面先来启动代理,直接运行下面的命令即可。

crank-agent

会看到下面的输出:

[11:42:30 INF] Created temp directory 'C:\Users\catcherwong\AppData\Local\Temp\2\benchmarks-agent\benchmarks-server-8952\2mmqc00i.3b1'[11:42:30 INF] Agent ready, waiting for jobs...

默认端口是 5010,可以通过 -u|--url 来指定其他的;如果运行代理的电脑已经安装好 SDK 了,可以指定 --dotnethome 避免因网络问题导致无法正常下载 SDK。

然后是通过控制器向代理发送指令。

crank --config C:\code\crank\samples\micro\micro.benchmarks.yml --scenario Md5VsSha256 --profile local

上面的命令指定了我们上面的配置文件,同时还指定了 scenario 和 profile。因为配置文件中可以有多个 scenario 和 profile,所以在单次执行是需要指定具体的一个。

如果需要执行多个 scenario 则需要执行多次命令。

在执行命令后,代理里面就可以看到日志输出了:

最开始的是收到作业请求,然后安装对应的 SDK。安装之后就会对指定的项目进行 release 发布。

发布成功后就会执行 BenchmarkDotNet 相关的内容。

运行完成后会输出结果,最后清理这次基准测试的内容。

代理执行完成后,可以在控制器侧看到对应的结果:

一般来说,我们会把控制器得到的结果保存在 JSON 文件里面,便于后续作对比或者要出趋势图。

这里可以加上 --json 文件名.json

crank --config C:\code\crank\samples\micro\micro.benchmarks.yml --scenario Md5VsSha256 --profile local --json base.json

运行多次,将结果存在不同的 JSON 文件里,尤其代码变更前后的结果。

crank --config C:\code\crank\samples\micro\micro.benchmarks.yml --scenario Md5VsSha256 --profile local --json head.json

最后是把这两个结果做一个对比,就可以比较清楚的看到代码变更是否有带来提升。

crank compare base.json head.json

上面提到的还是在本地执行,如果要在不同的机器上面执行要怎么配置呢?

我们要做的是在配置文件中的 profiles 节点增加机器的代理地址即可。

下面是简单的示例:

profiles:  local:    jobs:      application:        endpoints:          - http://localhost:5010  remote-win:    jobs:      application:        endpoints:          - http://192.168.1.100:9090  remote-lin:    jobs:      application:        endpoints:          - http://192.168.1.102:9090      

这个时候,如果指定 --profile remote-win 就是在 192.168.1.100 这台服务器执行基准测试,如果是 --profile remote-lin 就是在 192.168.1.102

这样就可以很轻松的在不同的机器上面执行基准测试了。

Crank 还有一个比较有用的功能是可以针对 Pull Request 进行基准测试,这对一些需要基准测试的开源项目来说是十分有帮助的。

接下来老黄就着重讲讲这一块。

Pull Request

正常来说,代码变更的肯定是某个小模块,比较少出现多个模块同时更新的情况,如果是有,估计也会被打回拆分!

所以我们不会选择运行所有模块的基准测试,而是运行变更的那个模块的基准测试。

思路上就是有人提交 PR 后,由项目组成员在 PR 上面进行评论来触发基准测试的执行,非项目组成员的话不能触发执行。

下面就用这个 Crank 提供的 Pull Request Bot 来完成后面的演示。

要想用这个 Bot 需要先执行下面的安装命令:

dotnet tool update Microsoft.Crank.PullRequestBot --version "0.2.0-*" --global

安装后会得到一个 crank-pr 的文件,然后执行 crank-pr 的命令就可以了。

可以看到它提供了很多配置选项。

下面是一个简单的例子

crank-pr \  --benchmarks lib-dosomething \  --components lib \  --config ./benchmark/pr-benchmark.yml\  --profiles local \  --pull-request 1 \  --repository "https://github.com/catcherwong/library_with_crank" \  --access-token "${{ secrets.GITHUB_TOKEN }}" \  --publish-results true

这个命令是什么意思呢?

它会对 catcherwong/library_with_crank 这个仓库的 Id 为 1 的 Pull Request 进行两次基准测试,一次是主分支的代码,一次是 PR 合并后的代码;基准测试的内容由 benchmarks,components 和 profiles 三个选项共同决定;最后两个基准测试的结果对比会在 PR 的评论上面。

其中 catcherwong/library_with_crank 是老黄提前准备好的示例仓库。

下面来看看 pr-benchmark.yml 的具体内容

components:    lib:        script: |            echo lib        arguments:            # crank arguments            "--application.selfContained false"# default arguments that are always used on crank commandsdefaults: ""# the first value is the default if none is specifiedprofiles:    local:      description: Local      arguments: --profile local    remote-win:      description: windows      arguments: --profile remote-win    remote-lin:      description: linux      arguments: --profile remote-lin benchmarks:    lib-dosomething:      description: DoSomething      arguments: --config ./benchmark/library.benchmark.yml --scenario dosomething    lib-getsomething:      description: GetSomething      arguments: --config ./benchmark/library.benchmark.yml --scenario getsomething    lib-another:      description: Another      arguments: --config ./benchmark/library.benchmark.yml --scenario another

基本上可以说是把 crank 的参数拆分了到了不同的配置选项上面去了,运行的时候就是把这些进行组合。

再来看看 library.benchmark.yml

jobs:  lib:    source:      localFolder: ../src      project: BenchmarkLibrary/BenchmarkLibrary.csproj    variables:      filter: "*"      jobArg: short    arguments: --job {{jobArg}} --filter {{filter}} --memory    options:      benchmarkDotNet: true  scenarios:  dosomething:    application:      job: lib      variables:        filter: "*DoSomething*"  getsomething:    application:          job: lib      variables:        filter: "*GetSomething*"  another:    application:          job: lib      variables:        filter: "*Method*"profiles:  local:    jobs:      application:        endpoints:          - http://localhost:9999    remote-lin:    jobs:      application:        endpoints:          - http://remote-lin.com  remote-win:    jobs:      application:        endpoints:          - http://remote-win.com

和前面入门的例子有点不一样,我们在 scenarios 节点 里面加了一个 variables,这个和 jobs 里面定义的 variables 和 arguments 是相对应的。

如果指定 --scenario dosomething,那么最后得到的 arguments 就是

--job short --filter *DoSomething* --memory

后面就是来看看效果了。


使用Crank给我们的类库做基准测试的评论 (共 条)

分享到微博请遵守国家法律