跳到主要内容
英文版本为准

本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。

方法论

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。某次运行突破了上限,也仍会跑完所有场景并打印表格。表格下面列出每一个被突破的 上限,用来对照的值和新值并排给出,然后这次运行以失败结束。