分享 本地测试了下个人常用的 Web 框架的吞吐率,结果不出意料

small_fish__ · 2016年05月27日 · 最后由 ksec 回复于 2016年05月28日 · 9268 次阅读

目的

测试个人经常使用的不同 App server(主要用来做 API)的吞吐率。

测试主机

  • 系统:MacBook Air (11-inch, Early 2015)
  • CPU: 1.6 GHz Intel Core i5 x4 (4 核)
  • 内存:4 GB 1600 MHz DDR3

参数对比

1. Golang Gin 框架(协程模式)
  • Go 1.6
  • Gin v1.0rc1
2. Node Koa 框架 (多进程 + 异步 IO 模式)
  • Node v6.1.0
  • Koa 1.2.0
3. Ruby Sinatra 框架 (多进程多线程模式)
  • Ruby 2.3.1p112
  • Sinatra 1.4.7
  • Puma 3.4.0

测试方法

数据准备

采用 mongodb 作为测试,并选用了各自最为流行的 mongo 驱动。

进入 mongo console 执行以下命令:

use ruby2go2node2elixir

db.users.insert([
  {"_id": ObjectId(), "username": "Josh Williams", "created_at": new Date(), "updated_at": new Date()},
  {"_id": ObjectId(), "username": "Josh Williams", "created_at": new Date(), "updated_at": new Date()},
  {"_id": ObjectId(), "username": "Josh Williams", "created_at": new Date(), "updated_at": new Date()},
  {"_id": ObjectId(), "username": "Josh Williams", "created_at": new Date(), "updated_at": new Date()},
  {"_id": ObjectId(), "username": "Josh Williams", "created_at": new Date(), "updated_at": new Date()}
])

启动服务:

所有命令都是在项目根目录前提下执行:

Gin (golang)(绑定在 3000 端口)
cd go
source env.sh
make 
Koa (nodejs)(绑定在 3001 端口)
cd nodejs
npm install
npm start
Sinatra (ruby)(绑定在 3002 端口)
cd ruby
bundle 
puma -C puma.rb

Wrk 本地测试结果

Gin (golang)

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3000/static"
Running 30s test @ http://127.0.0.1:3000/static
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    14.63ms   59.10ms 779.91ms   96.68%
    Req/Sec     5.42k     1.37k   20.49k    86.36%
  634806 requests in 30.10s, 167.09MB read
Requests/sec:  21092.78
Transfer/sec:      5.55MB

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3000/users"
Running 30s test @ http://127.0.0.1:3000/users
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    17.63ms    5.65ms 125.03ms   80.56%
    Req/Sec     1.43k   317.13     2.19k    59.50%
  171245 requests in 30.03s, 143.06MB read
Requests/sec:   5702.07
Transfer/sec:      4.76MB

Koa (nodejs)

#### 多进程模式

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3001/static"
Running 30s test @ http://127.0.0.1:3001/static
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    10.77ms    8.97ms 199.54ms   94.74%
    Req/Sec     2.56k   374.25     4.50k    87.25%
  305517 requests in 30.03s, 84.20MB read
Requests/sec:  10172.22
Transfer/sec:      2.80MB

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3001/users"
Running 30s test @ http://127.0.0.1:3001/users
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    44.25ms   18.20ms 460.36ms   85.57%
    Req/Sec   576.93     83.73     0.85k    76.90%
  68859 requests in 30.01s, 56.08MB read
Requests/sec:   2294.72
Transfer/sec:      1.87MB

##### 单进程模式

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3001/static"
Running 30s test @ http://127.0.0.1:3001/static
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    21.21ms    1.45ms  47.63ms   93.30%
    Req/Sec     1.18k   103.09     1.88k    86.25%
  141520 requests in 30.06s, 39.00MB read
Requests/sec:   4708.48
Transfer/sec:      1.30MB

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3001/users"
Running 30s test @ http://127.0.0.1:3001/users
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    73.37ms   10.07ms 212.29ms   87.93%
    Req/Sec   342.42     66.59   505.00     63.45%
  40923 requests in 30.08s, 33.33MB read
Requests/sec:   1360.40
Transfer/sec:      1.11MB

Sinatra (ruby)

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3002/static"
Running 30s test @ http://127.0.0.1:3002/static
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    22.83ms   28.96ms 392.78ms   88.99%
    Req/Sec     1.23k   334.43     2.07k    60.92%
  147405 requests in 30.06s, 35.85MB read
Requests/sec:   4903.72
Transfer/sec:      1.19MB

wrk -t4 -c100 -d30S --timeout 2000 "http://127.0.0.1:3002/users"
Running 30s test @ http://127.0.0.1:3002/users
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   153.62ms  121.39ms   1.09s    73.08%
    Req/Sec   159.87     36.28   287.00     69.44%
  19111 requests in 30.05s, 16.51MB read
Requests/sec:    635.96
Transfer/sec:    562.70KB

吞吐率倍数关系

  • 无 DB 查询:

gin > koa(多进程) > koa(单进程) ~= sinatra   
   (2x)          (2x)
  • 有 DB 查询: bash gin > koa(多进程) > koa(单进程) > sinatra (2x) (2x) (2x)

结论:

  1. 在 4 核 CPU 条件下,golang 并发大概是 Nodejs 的两倍,Ruby 8 倍。因为 golang 有更好的并发模型,所以核数越多,这个差距越大。
  2. 多进程的并发模型,代价其实挺大的。比如 Node 多进程模式并发能力并没有随核数成倍增加,大致是其核数/2 的提升。
  3. Ruby GIL 缘故,其并发能力最差不足为奇。但是现在主流的都是微服务,RESTful API,一些性能要求较高的 业务完全可以考虑其他性能更好的语言。在其他语言领域,有些 ruby 棘手的问题,或许根本就不存在。

测试代码,请移步 https://github.com/songjiayang/ruby2go2node2elixir ,如果有不合理的地方,请大家指出。 ps: 我也希望是我测试代码写的有问题导致 Ruby 结果不理想。

据说 Ryan Dalh 弄 nodejs 的时候考虑过用 ruby 来写,结果因为虚拟机性能太差被放弃。

看文章开头就知道 go 应该是并发最好的了。

不但性能有差距,内存消耗的差距也非常大...

#3 楼 @cxh116 是的,主要 fork 进程导致

怎么没有 elixir 的?

#5 楼 @numbcoder 计划中,项目名字可以看出,准备使用 maru

看到 new Date(),我就觉得这是不公平的竞争了,建议把日期做个 memoization,再重新 benchmark

#7 楼 谢谢 @nouse 提醒,此处 new Date() 只是用作初始化数据库,待真正测试的时候,使用相同的数据库,所以这里应该没啥问题。

这种测试没啥用,你的业务实际情况又不可能这么简单。

#9 楼 @huacnlee 有些 api 还是比较简单的

简单的 case 都比不过的话复杂的 case 下能比得过么?

#10 楼 @small_fish__

  1. gin app -> go 本身的 net/http server 甚至优化过的 HttpRouter
  2. express app -> node 本身的 net/http server
  3. sinatra app -> rack -> puma

测试用例看上去差不多,其实只是 app 层面代码复杂度接近,单个请求回路其实可能差距甚远。 如果 app 本身代码一旦复杂起来,可能单个回路的复杂度就差异没有那么明显了。hello world 级别的 benchmark 意义不大。

gin 本身还宣称可以比同为 go 写的 martini 快最多 40 倍,这个就不是并发模型可以解释的了。 比较说得通的是语言本身性能有差距,http server 优化有差异,总体代码复杂度也差很多。

另外有带 vm 的语言 warm up 一下可能会表现有所提高,虽然 YARV 在这方面几乎就是渣渣。

#12 楼 @serco

我测试过,当并发很小的时候,ruby 性能还可以,速度和 node 基本一样快,但 并发数量逐渐提高,ruby 性能直线下降(单个请求返回时间)。当然,ruby 速度却是比 go 慢一些。

既然测试代码用了 new Date(),那么我来说个 new Date() 的 Javascript 段子。

> new Date() > new Date()
false
> new Date() < new Date()
false
> new Date() >= new Date()
true
> new Date() <= new Date()
true
> new Date() == new Date()
false
> new Date() === new Date()
false

Go 在简单业务中的确快。(符合楼主说的,结果不出意料) 写 Ruby 比写 Javascript 舒服好多,所以 Javascript 即使能快一些也没吸引力,原因见上方段子。

当数据库和 IO 成为瓶颈以后(99% 需要性能调优的场景),语言层面的并发性能都是浮云。

还是 DHH 说的好:赚不到兰博基尼,就嫌慢... 我就不嫌弃,因为我有兰博。

单性能测试,除误导眼球,基本没啥用

最后总结下来其实就是工程师值钱还是服务器值钱的问题了。 服务器永远会越来越便宜,工程师永远会越来越贵。

工程师的单点效率也会越来越贵。

一个 node 项目。连 package.json 都不用的测试,敢相信么?

#18 楼 @i5ting 有得哟

#15 楼 @kgen 完全认同,大多数网站瓶颈都在 IO 操作上。那个段子真的太有意思了,我也更喜欢写 Ruby。

我现在对于 Ruby 的慢倒并不在意,我不欣赏的是 Ruby 解决速度慢的方式:写 C 扩展

对于任何语言来说,使用 C 扩展都是很丑陋的事情,比如 Go 的创始人就总结 cgo is not Go(http://dave.cheney.net/2016/01/18/cgo-is-not-go)。 而 Ruby 却一直保持代码一慢就写 C 的传统。

alt Backend vs Frondend 这张图来形容 Ruby 也不为过,Ruby 程序员每天宣传 Ruby 美妙让大家 happy,结果真正 kick ass 反而都是丑陋的代码。

#20 楼 @nouse 确实,此图非常形象,不过不是有 Ruby 3x3 么,希望早点实现,VM 大大提升就会好很多。

#21 楼 @small_fish__ Ruby 3x3 意思是 Ruby 3 比 Ruby 2.0 快 3 倍。但 Ruby 2.0 - Ruby 2.3 已經快 大約 50% 了,也就是說 Ruby 3.0 只會比 Ruby 2.3 快 2 倍左右。 事實上 Ruby 亦試過多種方法,包括像 LuaJIT 的 TraceJIT, https://github.com/imasahiro/rujit 雖然快 5 - 20 倍但因為 Memory 太大而放棄,Method JIT 因為無有太大 Speed Difference 而放棄。 而且以上兩種對 Metaprogramming 速度也快不了多小。

暫時最有優勢的是 JRuby ( Graal + Truffle ),很可昔 Ruby 對 Anything Java 感覺太有敵意。

#20 楼 @nouse Rust http://blog.skylight.io/introducing-helix/ https://news.ycombinator.com/item?id=11698251 Crystal http://www.mikeperham.com/2016/05/25/sidekiq-for-crystal/

需要 登录 后方可回复, 如果你还没有账号请 注册新账号