背景

当我们写了一个类库提供给别人使用时,我们可能会对它做一些基准测试来测试一下它的性能指标,好比内存分配等。

在 .NET 的世界中,用 BenchmarkDotNet 来做这件事是非常不错的选择,我们只要写少量的代码就可以在本地运行基准测试然后得到结果。

这个在修改代码的时候,效果可能会更加明显,因为我们想知道我们的修改会不会使这段代码跑的更快,占用的资源更少。

作一个简单的假设,根据测试用例,代码变更之前,某方法在基准测试的分配的内存是 1M,修改之后变成 500K,那么我们可以认为这次的代码变更是有性能提升的,占用的资源更少了,当然这个得在单元测试通过的前提下。

试想一下,如果遇到下面的情况

  1. 想在多个不同配置的机器上面运行基准测试,好比 4c8g 的windows, 4c16g 的 linux
  2. Pull Request/Merge Request 做代码变更时,如何较好的做变更前后的基准测试比较

这个时候就会复杂一点了,要对一份代码在多个环境下面运行,做一些重复性的工作。

那么我们有没有办法让这个变得简单呢?答案是肯定的。

我们可以用 Crank 这个工具来完成这些内容。

什么是 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-*" --global

dotnet 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 commands
defaults: "" # the first value is the default if none is specified
profiles:
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

后面就是来看看效果了。

这里省略了评论内容的解析,也就是评论什么内容的时候会触发执行,因为这一块不是重点,有兴趣可以看 workflow 的脚本即可。

具体的执行过程可以参考

https://github.com/catcherwong/library_with_crank/actions/runs/4598397510/jobs/8122376959

当然,如果条件允许的话,也可以用自己的服务器资源来跑基准测试,不用 Github Action 提供的资源。

这样的好处是相对稳定,可以自己根据场景指定不同配置的服务器。不过对一些没那么复杂类库,用 Github Action 的资源也是无伤大雅的。

下面这个截图就是在提交到外部服务器上面执行的。

如果仓库不是在 Github,是在自建 Gitlab 或者其他的,就可以根据这个思路来自定义流水线从而去完成这些基准测试的操作。

总结

Crank 还是一个挺不错的工具,可以结合 BenchmarkDotNet 来做类库的基准测试,也可以结合 wrk/wrk2/bombardier/h2load 等压测工具进行 api/grpc 框架和应用的测试。

这里只介绍了其中一个小块的内容,还有挺多内容可以挖掘一下的。

最后是本文的示例代码:

https://github.com/catcherwong/library_with_crank

参考资料

聊一聊如何使用Crank给我们的类库做基准测试的更多相关文章

  1. C++使用RabbitMQ类库做客户端与RabbitMQ Server通讯,生成C++可调用的rabbimq.*.dll的过程

    Step: download the latest rabbitmq-c via: https://github.com/alanxz/rabbitmq-c follow the document, ...

  2. 编写Javascript类库(jQuery版) - 进阶者系列 - 学习者系列文章

    这些年主要关注于项目管理方面的工作,编码就比较少了.这几天比较空闲,就想把原来的经验沉淀下来,一个是做好记录,以后如果忘记了还能尽快找回来,第二个是写写博文,算是练练手笔吧. 言归正传,这次写的是Ja ...

  3. iOS平台XML解析类库对比和安装说明

    在iPhone开发中,XML的解析有很多选择,iOS SDK提供了NSXMLParser和libxml2两个类库,另外还有很多第三方类库可选,例如TBXML.TouchXML.KissXML.Tiny ...

  4. 优雅地使用CodeIgniter 3之Session类库(1)(转)

    相信无数人在使用CI2的Session类库时,遇到各种的坑,各种抱怨,各种不解.在CI中国论坛能搜到大量关于Session类库的提问,说明要想用 好session类库还是得下一番功夫.本文将先从CI2 ...

  5. Windows.Andy.Code4App.dll Win8.1/WP8.1通用类库@ver1.0.1

    在上篇 Windows.Andy.Code4App.dll  Win8.1/WP8.1通用类库@ver1.0.0 已经对Win8.1和WP8.1部分扩展通用类库做了说明,这篇继续对通用类库做扩展.写的 ...

  6. 编写Javascript类库(jQuery版

    编写Javascript类库(jQuery版) - 进阶者系列 - 学习者系列文章 Posted on 2014-11-13 09:29 lzhdim 阅读(653) 评论(1) 编辑 收藏 本系列文 ...

  7. Java——容器类库框架浅析

    前言 通常,我们总是在程序运行过程中才获得一些条件去创建对象,这些动态创建的对象就需要使用一些方式去保存.我们可以使用数组去存储,但是需要注意数组的尺寸一旦定义便不可修改,而我们并不知道程序在运行过程 ...

  8. CamStar insitexmlclient重新封装为.net Core类库

    工作原因经常使用camstar的 InsiteXMLClient类库做二次开发,但是只能在4.X环境下使用,对于日益繁荣的.net core生态,花费了些时间把原有的类库重新封装为.net core ...

  9. Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用(后续)

    在[Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用]里面提到了Microsoft 身份认证,其实这也是一大块需要注意的地方,特作为后续补充这些知识点.上章是使用了Microsof ...

  10. ABP理论之CSRF

    返回总目录 本篇目录 介绍 ASP.NET MVC ASP.NET WEB API ASP.NET Core[以后补上] 客户端类库 内部原理 介绍 CSRF[Cross-Site Request F ...

随机推荐

  1. js 原生数据类型判断

    之前一直使用的jquery的数据类型判断,比如:isArray()等,今天看到了一个判断数据类型的简单的原生方法,分享给大家 Object.prototype.toString 方法返回对象的类型字符 ...

  2. PLC入门笔记11

    1.开关? 输入 拨杆开关.点动开关.常开.常闭开关 霍尔接近开关(磁场 N极导通 3线+-DC24V ).电容接近开关(非金属).电感接近开关(金属) 2.输入接线? NPN型,不需要外接电源,直接 ...

  3. python+selenium+unittest自动化测试

    目前先用这个记录自动化测试相关内容,后期再进行整理: 1.自动化测试:testcase-->test suite  ---> TestRunner 2.TestRunner时,一种将内容打 ...

  4. 12.20linux学习第十九天

    今天老刘开始讲第17章 使用iSCSI服务部署网络存储.第18章 使用MariaDB数据库管理系统和第19章 使用PXE+Kickstart无人值守安装服务,内容有点多. 7.1 iSCSI技术介绍 ...

  5. Manage your references to .Net assemblies Dynamics 365 for Operations VS projects

    (Dynamics 365 for Operations was previously known as the New Dynamics AX) Dynamics 365 for Operation ...

  6. 持续集成环境(2)-Jenkins插件管理

    Jenkins本身不提供很多功能,我们可以通过使用插件来满足我们的使用.例如从Gitlab拉取代码,使用 Maven构建项目等功能需要依靠插件完成.接下来演示如何下载插件. 修改Jenkins插件下载 ...

  7. 在VMWare里安装Win11虚机

    1. 安装win11有最低硬件要求 64位CPU双核,内存4G,硬盘64G,受信任的平台模块(TPM)2.0,支持UEFI安全启动 2. VMware新建虚机的设置 1)创建64位虚拟机,CPU设置为 ...

  8. HTTP 认证授权技术归纳

    原文:https://coolshell.cn/articles/19395.html

  9. redis基础-redis事务

    学习总结 原文:https://juejin.im/post/5d29ac845188252cc75e2d5c redis事务: redis是否有事务? redis是有事务的.命令如下: Redis事 ...

  10. Kibana+X-pack安装使用

    安装Kibana 下载解压安装包,一定要装与ES相同的版本 下载地址: https://www.elastic.co/downloads/kibana wget https://artifacts.e ...