本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。
方法论
ruststream-gcp-pubsub 怎样测量自己:三个循环、在模拟器上的运行、受 Broker 限制的算术,以及代码计数。
这套流程属于框架本身(RustStream 方法论),
这一页说明它在 Pub/Sub 上是什么样子。
三个循环
同一个进程用三个循环跑每个场景,它们只差一件事:由什么来承载消息。
- 裸客户端。 一个手写的循环直接驱动
google-cloud-pubsub。 - Broker crate。 只有这个 crate,不含其上的任何东西:它的订阅、它交出的投递、它的发布者,由一个 外面没有服务的循环来读取和确认。
- RustStream。 用户会写的服务:
#[subscriber]处理器、应用和运行时。
其余一切在三个循环之间都相同:端点和凭据、拓扑、流式拉取的设置、确认(ack)的位置、解码成同一个类型、 载荷字节、tokio 运行时和构建。
每一个数字都取自 docker-compose.test.yml 起的本地模拟器,没有一个取自云上的服务。
运行
- 测量窗口从第一次投递开始,到最后一次投递被确认为止,每个循环都是这样。
- 消息条数由一次试探运行定下来,使得每一次被测量的运行在所在机器上都不短于五秒。
- 各循环交错运行,共三轮。每个循环报告最佳、中位和最差的一轮。
当一行的速度由模拟器决定时,它就被标为「受 Broker 限制」。这个标记由算术定出:一次在各轮之外的探测量出 到模拟器的一个往返,一次投递按它的确认计一个往返,当这个乘积覆盖了一条消息所用时间的一半以上时,这一行 就被标记。往返时间和机器一起公布,算术可以核对。
代码计数
开销图的每个场景都是用户会写的那种服务:它建在 PubSubBroker 上,对着同一套
环境里的模拟器启动。服务只在一个线程上运行,这个线程上的一切都计入:框架的分发、本 crate 的代码,以及
google-cloud-pubsub 客户端,它的流式拉取、确认、确认截止时间的延长和发布请求都在这里运行。模拟器是另一个
进程,不在数字里。消息在开始消费之前从另一个线程发布,这部分工作也不在数字里。
指令数由 callgrind 统计,内存分配次数由 DHAT 统计。两者都是稳态下每条消息的值:1000 次投递的运行和 2000 次投递的运行之间的斜率。冷启动是启动服务并处理第一次投递一次性付出的开销。
客户端按批确认,并按定时器延长确认截止时间,所以每次运行的计数都会有一点浮动:五次运行里,每条消息
的指令数相差不超过百分之 5;最长那次运行的分配次数,回复那一行相差不超过 3500 次,另外两行不超过
440 次。just bench-code 在分配次数超过场景声明的下限时失败:下限是见到过的最大计数,再加上不小于
这个浮动、也不小于千分之一的余量。加上 --baseline=main 时,指令数多出百分之二以上也算失败。改变
开销的合并请求要附上自己的数字。
自己运行
just bench
这条 recipe 从 docker-compose.test.yml 起停模拟器,跑完两个场景,然后把测到的结果写回
docs/benchmarks/results.json。它要花几分钟,并且需要整台机器。它会清空 RUSTFLAGS,所以二进制不
绑定于构建它的那颗 CPU。两条 recipe 都按发布版本的方式构建基准测试,跨 crate 启用完整的链接时优化
(fat LTO),因此这些数字描述的正是服务实际使用的代码。
just bench-code
这条 recipe 先起模拟器,在 valgrind 下统计代码开销,再把模拟器停掉,并重写同一份文档里的 code
部分。它要花几分钟。除了 Docker,还需要 valgrind。基准测试运行器必须是 Cargo.lock 锁定的那个
gungraun 版本:这条 recipe 第一次运行时会自己把它装进 target/,版本变了以后会再装一次。
recipe 名后面的数字设定每次被测量的运行包含多少次投递(just bench-code 500);公布的数字按
默认的 1000 次测量。其余参数交给基准测试运行器,例如 --save-baseline=main 或
--baseline=main。某次运行突破了上限,也仍会跑完所有场景并打印表格。表格下面列出每一个被突破的
上限,用来对照的值和新值并排给出,然后这次运行以失败结束。