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

news2024/11/12 5:56:46

背景

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

在 .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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【小白必看】Python爬取NBA球员数据示例

文章目录 前言导入需要的库和模块设置请求头和请求地址发送HTTP请求并获取响应处理响应结果解析数据将结果保存到文件完整代码详细解析 运行效果结束语 前言 使用 Python 爬取 NBA 球员数据的示例代码。通过发送 HTTP 请求,解析 HTML 页面,然后提取出需要…

imgcat命令行查看图片

背景 昨天在哔哩哔哩上看到了mac控制台工具imgcat 可以实现在控制台查看图片,我觉得太酷炫了!于是手动的安利一下。 下载工具 curl "https://iterm2.com/utilities/imgcat" > imgcat执行权限 chmod x imgcat第一次使用 ./imgcat ~/img…

RocketMQ发送消息还有这种坑?遇到SYSTEM_BUSY不重试?

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党 RocketMQ版本 5.1.0 背景 最近线上的RocketMQ集群遇到了如下问题,业务方的小伙伴反馈问题,说出现了 MQBrokerException:CO…

C++数据结构笔记(10)递归实现二叉树的三序遍历

对于三种遍历方式来说,均为先左后右!区别在于根结点的位置顺序 先序遍历:根——左——右 中序遍历:左——根——右 后序遍历:左——右——根 (所谓先中后的顺序,是指根结点D先于子树还是后于…

力扣算法练习(四)

1.盛水最多的容器(11) 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明:…

Vue 常用指令 v-on 自定义参数和事件修饰符

自定义参数就是可以在触发事件的时候传入自定义的值。 文本框,绑定了一个按钮事件,对应的逻辑是sayhi,现在无论按下什么按钮都会触发这个sayhi。但是实际上不是所有的按钮都会触发,只会限定某一些按钮,最常见的按钮就…

linux下nginx的安装和使用

文章目录 📒安装nginx1️⃣上传到对应目录2️⃣解压nginx3️⃣检查是否启动成功 📒使用nginx1️⃣简单的反向代理2️⃣介绍location配置中root和alias的区别 📒安装nginx 官网下载: https://nginx.org/ 我这里自己下载的是稳定版本 nginx-1.…

手撕顺序表

> 作者简介:დ旧言~,目前大一,现在学习Java,c,c,Python等 > 座右铭:松树千年终是朽,槿花一日自为荣。 > 望小伙伴们点赞👍收藏✨加关注哟💕&#x1…

标准IO_文件读写_fgetc,getchar,ungetc,fgets,fputs,fread,fwrite

目录 1.单字符文件读写 1.1 单字符读文件 1.1.1 fgetc函数 1.1.2 getc函数 1.1.3 getchar函数 1.1.4 ungetc函数 1.1.5 单字符读文件综合示例代码 1.2 单字符写文件 1.2.1 fputc函数 1.2.2 putc函数 1.2.3 putchar函数 1.2.4 单字符写文件综合示例代码 2.多字符文…

c++11 标准模板(STL)(std::basic_filebuf)(七)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_filebuf : public std::basic_streambuf<CharT, Traits> std::basic_filebuf 是关联字符序列为文件的 std::basic_streambuf 。输入序…

python核心-面向对象-三大特性:封装,继承,多态

封装 继承 # class Animal: # pass # # class xxx: # pass # # class Dog(Animal,xxx): # pass # # # d Dog() # print(d.__class__) # print(Dog.__class__) # # # print(Dog.__bases__) # print(Animal.__bases__) # # object# ---------------------继承-资源…

最小环计数

Problem - 7322 思路&#xff1a;跑一个floyd&#xff0c;同时求出最小环以及进行最小环计数&#xff0c;对于每个环&#xff0c;我们每次用编号最大的点进行统计&#xff0c;防止重复 以下是模板 for(int k1;k<n;k) {for(int i1;i<n;i) {for(int j1;j<n;j) {if(dis…

java快速生成数据库表文档(HTML、DOC、MD)

在企业级开发中、我们经常会有编写数据库表结构文档的时间付出&#xff0c;关于数据库表结构文档状态&#xff1a;要么没有、要么有、但都是手写、后期运维开发&#xff0c;需要手动进行维护到文档中&#xff0c;很是繁琐&#xff0c;这里推荐一个开源项目&#xff1a;screw gi…

读数据压缩入门笔记09_多媒体数据压缩

1. 压缩分类 1.1. 多媒体数据压缩&#xff08;media-specific compression&#xff09; 1.2. 通用压缩&#xff08;general purpose compression&#xff09; 2. 有损压缩算法 2.1. 为了使数据压缩得更小&#xff0c;可以牺牲多媒体的质量这样的数据转换 2.2. 针对特定的多…

Kaggle灾难推文的自然语言处理-最佳得分详解

目录 灾难推文的自然语言处理——预测哪些推文是关于真实灾难的&#xff0c;哪些不是。 一、比赛概述 二、数据集 三、代码 0.导入库与数据 1. 关键字和位置 1.1 缺失值 1.2 元素和目标分布 2. 元特征 3. 目标和N-grams 3.1 目标 3.2 unigrams 一元语法 3.3 big…

[回馈]ASP.NET Core MVC开发实战之商城系统(三)

经过一段时间的准备&#xff0c;新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始&#xff0c;在之前的文章中&#xff0c;讲解了商城系统的整体功能设计&#xff0c;页面布局设计&#xff0c;环境搭建&#xff0c;系统配置&#xff0c;及首页【商品类型&#xff0c;ba…

如何使用ArcGIS Pro制作越来越真实的水域效果

制图的目的就是为了让图更加的美观&#xff0c;这里我们以水域效果为例&#xff0c;为大家讲解一下如何一步步制作越来越真实的水域效果&#xff0c;希望能对你有所帮助。 常规效果 使用过ArcMap制图的朋友应该比较熟悉&#xff0c;可以在符号系统内修改一下填充颜色&#xff…

启用、禁用员工账号

接口相关信息 controller层 /** 启用禁用员工账号* */PostMapping("/status/{status}")ApiOperation("启用禁用员工账号")public Result startOrStop(PathVariable Integer status, Long id) {log.info("启用禁用员工{}&#xff0c;{}",status,i…

Bert模型及变体

ALBert ALBERT就是为了解决模型参数量大以及训练时间过长的问题。ALBERT最小的参数只有十几M, 效果要比BERT低1-2个点&#xff0c;最大的xxlarge也就200多M。可以看到在模型参数量上减少的还是非常明显的&#xff0c;但是在速度上似乎没有那么明显。最大的问题就是这种方式其实…

Kubernetes 之CNI 网络插件对比

介绍 网络架构是Kubernetes中较为复杂、让很多用户头疼的方面之一。Kubernetes网络模型本身对某些特定的网络功能有一定要求&#xff0c;但在实现方面也具有一定的灵活性。因此&#xff0c;业界已有不少不同的网络方案&#xff0c;来满足特定的环境和要求。 CNI意为容器网络接…