现代的异步编程中有如下的几个概念
由于协程是非常轻量的,所以可以在一个进程中大量的创建,runtime 会实际创建系统线程(一般为恰好的物理 CPU 数),并将协程映射到实际的物理线程上执行,这个有时候称为 M:N模型。好的 runtime 会使得系统整体的性能随着物理 CPU 的增加而线性增加。
Golang 是原生支持上述模型的语言,这也是 Golang 与众不同的主要特性,在 Golang 中,通过关键词 go 即可轻松开启一个协程,通过关键词 chan 则可以定义一个队列,Golang 内置了调度运行时来支撑异步编程。
Rust 在 2019 年的 1.39 版本中,加入 async/.await 关键词,为异步编程提供了基础支撑,之后,随着 Rust 生态中的主要异步运行时框架之一 tokio 1 发布,Rust 编写异步系统也变得跟 Golang 一样方便。
Kotlin 是一个基于 JVM 的语言,它语言层面原生支持协程,但由于 JVM 现在还不支持协程,所以它是在 JVM 之上提供了的调度运行时和队列。顺便,阿里巴巴的 Dragonwell JDK 在 OpenJDK 的基础上可以选择开启 Wisp2 特性,来使得 JVM 中的 Thread 不再是系统线程,而是一个协程。JDK 19 开始增加了预览版的轻量级线程(协程),也许在下一个 JDK LTS 会有正式版。
下表对比了使用这两种语言对异步编程的特性支持
| Golang | Rust | Kotlin | |
|---|---|---|---|
| 协程 | 语言内置 | 由异步运行时框架提供 | 语言内置 |
| 队列 | 语言内置 | 由异步运行时框架提供 | 语言内置 |
| 调度运行时 | 语言内置,不可更改 | 多个实现, tokio/async_std/... | 语言内置 |
| 异步函数 | 无需区分 | 需显式的定义 | 需显式定义 |
| 队列类型 | 无需特指,只有一种 mpmc | 可特指,不同的场景提供不同实现 | 无需特指 |
| 垃圾回收 | 通过 GC 算法进行垃圾回收 | 无 GC,资源超出作用域即释放 | 通过 GC 算法进行垃圾回收 |
根据场景的不同,选择不同的队列,不同的运行时,可以得到更好的性能,但 Golang 和 Kotlin 简化了这些选择,一般来说,简化会带来性能的损失,本文测评 Go/Rust(tokio)/Kotlin 的调度和队列性能。
测评的逻辑如下
这个场景类似服务器的实现,当客户端连接到服务器时,创建一个协程,接收客户端的请求,然后将请求投递给处理协程。
在这样的逻辑下,有如下的几个参数来控制测评的规模
| 含义 | 命令行参数 | 说明 | |
|---|---|---|---|
| workers | 协程的数目 | -w | |
| events | 消息数目 | -e | |
| queue | 队列可堆积的消息的数目 | -q | 队列满了之后协程会阻塞 |
| etype | 消息的类型 | -t | 0 整数 1 字符串 2 字符串指针 3 字符串复制 |
| esize | 消息的大小 | -s | 对于字符串类似,越大的消息内存分配压力越大 |
测评完成后,会输出如下的几个数据
| 含义 | 说明 | |
|---|---|---|
| total_events | 总共产生和接收的消息数目 | 即 workers * events |
| time | 完成测试使用的需要的时间 | 越小越好 |
| speed | 每秒处理的消息数目 | total_events/time 越大越好 |
以下是各语言实现时的一些额外说明
Event 接口的不同 struct, 如 IntEvent, StrEvent, CheapStrEvent 等Event 接口的不同 struct, 如 IntEvent, StrEvent, CheapStrEvent 等--cpuprofile 文件名 参数来生成程序运行的性能分析数据boc-go/target/boc-go -w 10000 -e 10000 -q 256 --cpuprofile boc-go.pprof 然后可以通过 go tool pprof -http=:8081 boc-go.pprof 来查看boc-rs/target/release/boc-rs -c -w 10000 -e 10000 -q 256 --cpuprofile boc-rs.svg , 然后使用浏览器打开 boc-rs.svg 来查看在安装了 go、rust、JDK/maven 的机器上
git clone https://gitee.com/elsejj/bench-of-chain.git cd bench-of-chain make
感谢 腾讯云原生构建, 可以通过 cnb 来快速构建标准的运行环境, 而无需在本机上安装相关的工具
请在其页面中, Fork https://cnb.cool/elsejj/bench-of-chan, 然后点击 "云原生开发", 即可打开云上的IDE, 来进行编译和运行
run.sh 以相同的参数,同时运行各语言实现的程序,得到如下的输出$ ./run.sh -w 5000 -e 10000 -q 256 -t 2 program,etype,worker,event,time,speed golang,str_ptr,5000,10000,0.477,104845454 rust,str_ptr,5000,10000,0.652,76636797 kotlin,str_ptr,5000,10000,1.638,30526077
bench.sh 以不同的 worker 、etype 运行多次,输出结果列表,bench.sh 在不同的机器上,可能会运行数分钟, 其结果如$ ./run.sh -e 10000
| program | etype | worker | event | time | speed |
|---|---|---|---|---|---|
| golang | int | 100 | 10000 | 0.010 | 98969725 |
| rust | int | 100 | 10000 | 0.012 | 80789148 |
| kotlin | int | 100 | 10000 | 0.145 | 6917313 |
| golang | str | 100 | 10000 | 0.045 | 21989041 |
| rust | str | 100 | 10000 | 0.019 | 53630230 |
| kotlin | str | 100 | 10000 | 0.159 | 6304093 |
| golang | str_ptr | 100 | 10000 | 0.011 | 88775257 |
| rust | str_ptr | 100 | 10000 | 0.012 | 81436541 |
| kotlin | str_ptr | 100 | 10000 | 0.136 | 7340791 |
| ... | |||||
| kotlin | str_ptr | 50000 | 10000 | 12.434 | 40212992 |
| golang | int | 50000 | 10000 | 5.594 | 89376773 |
| rust | int | 50000 | 10000 | 9.131 | 54760465 |
| kotlin | int | 50000 | 10000 | 9.629 | 51927597 |
| golang | str | 50000 | 10000 | 17.794 | 28099233 |
| rust | str | 50000 | 10000 | 12.437 | 40203692 |
| kotlin | str | 50000 | 10000 | 16.774 | 29807544 |
| golang | str_ptr | 50000 | 10000 | 4.911 | 101819179 |
| rust | str_ptr | 50000 | 10000 | 8.795 | 56850205 |
| kotlin | str_ptr | 50000 | 10000 | 11.662 | 4287558 |
| 值 | |
|---|---|
| OS | Ubuntu 22.04 WSL on windows 11 64bit |
| CPU | Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz |
| Mem | 32G |
| Go | 1.18.1 |
| Rust | 1.62.0 |
| JDK | OpenJDK 17.0.3 |
| Kotlin | 1.7.10 |
| 值 | |
|---|---|
| OS | Ubuntu 24.04 64bit |
| CPU | Intel(R) Core(TM) i7-13700K |
| Mem | 64G |
| Go | 1.23.0 |
| Rust | 1.82.0 |
| JDK | OpenJDK 21.0.0 |
| Kotlin | 2.0.10 |
| CangJie | 0.5 |
| 值 | |
|---|---|
| OS | Ubuntu 24.04 64bit |
| CPU | Intel(R) Core(TM) i7-13700K |
| Mem | 64G |
| Go | 1.24.4 |
| Rust | 1.88.0 |
| JDK | OpenJDK 21.0.0 |
| Kotlin | 2.0.10 |
| CangJie | 1.0.0 |
| 值 | |
|---|---|
| OS | Debian 13 |
| CPU | AuthenticAMD * 8 |
| Mem | 16G |
| Go | 1.25 |
| Rust | 1.91 |
| JDK | OpenJDK 25 |
| Kotlin | 2.2 |
| CangJie | 1.0.4 |
./run.sh -e 10000
每个测评项会执行 5 次,取其平均值



从上述的运行结果来看
bench.sh 来进行相关的测试。