Ruby 2.1在今年圣诞节发布了,这是个好消息。它采用了更好的GC(RGenGC - gerational GC),层次化的方法缓存,一些小的语法变化和非实验性的Rfinements。总而言之,我们可以期待5%到15%的性能提升,这是相当棒的。
当我在阅读与发布有关的黑客新闻线程中的评论时,其中一条引起了我的注意--我们在MRI Ruby VM中需要JIT。好吧,但看起来每个人都忘记了Rubinius,它有一段时间是基于LLVM的JIT,本地线程,低暂停生成的垃圾收集器和几乎完美的C扩展支持。
是的,我们还有JRuby--它可能是最快的实现,当与TorqueBox这样的服务器结合时甚至更快,主要问题是:一些C语言库需要被替换,但正如人们在文章后面所看到的,这只是旧思维在起作用,显然现在情况要好得多,因为大多数宝石都支持JRuby,没有任何问题。
因此,我们在Ruby虚拟机之间有一个很好的中间地带:它支持两种C语言扩展,没有任何问题(然而,至少从经验上看,一些更奇特的宝石可能无法安装),但不知为何它被忽略了?
计划很简单,把一个生产中的Rails 4.0.2应用程序及其所有的依赖关系转换为Rubinius,安装Puma并做一些基准测试,然后如果一切正常就部署到staging。
设置
Rubinius将大部分标准库提取为宝石,所以为了正确启动任何使用这些宝石的Ruby脚本,需要在宝石文件中加入这个:
gem 'racc'
gem 'rubysl'
gem 'puma'
备注:
- rubysl - 是Ruby标准库gem的一个相当隐蔽的名字。
- racc 是 tenderlove 编写的 LALR(1) 解析器生成器 - 也是 Rubinius 的硬性要求,否则无法启动 Rails。
- puma - 这是Rubinius的最佳服务器选择,因为它支持本地线程。
不能与Rubinius一起使用的宝石(如果我找到更多的宝石,将会更新):
# gem 'oj'
记得把它们注释掉,否则Rails将无法启动。
对于虚拟机的安装和切换,我使用的是古老的RVM和最新版本的Rubinius 2.2.3以及Ruby 2.1.0。
现在来看看基准测试!
好吧,由于明显的原因,我不能分享应用程序的源代码,在某些时候,我可能会创建一个公共资源库,用于测试的东西。这些基准只针对一个特殊情况,也是为了好玩,所以在根据这些基准做出任何决定之前,请先在你自己身上测试。
Rails应用程序实际上是一个API,所以大部分重要的部分都被禁用了(例如,流式数据流 Rendering RequestForgeryProtection),还有链轮轨道。
ApacheBench配置
我使用了简单的ApacheBench,2.3版 - 显然不是理想的基准测试工具(应该使用siege或类似的工具),但对于像这个测试这样的快速浏览,它很适合工作。
启动它的命令:
ab -n400 -c16 -T'application/json' http://localhost:3000/entries
unicorn.rb
# config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true
before_fork do |server, worker|
# et cetera
end
启动它的命令: The command to start it up:
unicorn_rails -c config/unicorn.rb -p 3000
运行一段时间后的结果:
Concurrency Level: 16
Time taken for tests: 6.769 seconds
Complete requests: 400
Failed requests: 0
Write errors: 0
Total transferred: 3611600 bytes
HTML transferred: 3346800 bytes
Requests per second: 59.09 [#/sec] (mean)
Time per request: 270.766 [ms] (mean)
Time per request: 16.923 [ms] (mean, across all concurrent requests)
Transfer rate: 521.03 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 1.0 0 5
Processing: 49 267 36.8 270 331
Waiting: 44 266 36.8 269 330
Total: 49 267 36.3 270 331
Percentage of the requests served within a certain time (ms)
50% 270
66% 283
75% 288
80% 293
90% 308
95% 315
98% 324
99% 327
100% 331 (longest request)
puma.rb on Rubinius 2.2.3
# config/puma.rb
threads 8,32
workers 1
preload_app!
on_worker_boot do
# et cetera
end
启动它的命令:启动它的命令:启动它的命令。
puma -C config/puma.rb -b tcp://localhost:3000
多次运行后的结果(以便JIT能够发挥其魔力):
Concurrency Level: 16
Time taken for tests: 9.383 seconds
Complete requests: 400
Failed requests: 0
Write errors: 0
Total transferred: 3590400 bytes
HTML transferred: 3346800 bytes
Requests per second: 42.63 [#/sec] (mean)
Time per request: 375.311 [ms] (mean)
Time per request: 23.457 [ms] (mean, across all concurrent requests)
Transfer rate: 373.69 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 1
Processing: 84 371 105.4 348 731
Waiting: 83 363 104.1 338 728
Total: 84 371 105.3 348 732
Percentage of the requests served within a certain time (ms)
50% 348
66% 390
75% 431
80% 458
90% 526
95% 571
98% 640
99% 683
100% 732 (longest request)
puma.rb on JRuby 1.7.9
# config/puma.rb
threads 8,32
preload_app!
on_worker_boot do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
end
end
启动它的命令:启动它的命令:启动它的命令。
puma -C config/puma.rb -b tcp://localhost:3000
需要替换的宝石:
# gem 'pg'
gem 'activerecord-jdbcpostgresql-adapter'
多次运行后的结果(以便JIT能够施展它的魔法):
Concurrency Level: 16
Time taken for tests: 4.019 seconds
Complete requests: 400
Failed requests: 0
Write errors: 0
Total transferred: 3590400 bytes
HTML transferred: 3346800 bytes
Requests per second: 99.53 [#/sec] (mean)
Time per request: 160.760 [ms] (mean)
Time per request: 10.048 [ms] (mean, across all concurrent requests)
Transfer rate: 872.42 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 1
Processing: 35 158 31.5 157 389
Waiting: 34 151 27.1 149 261
Total: 36 158 31.5 157 389
Percentage of the requests served within a certain time (ms)
50% 157
66% 166
75% 173
80% 177
90% 189
95% 204
98% 232
99% 260
100% 389 (longest request)
结论
Rubinius的性能不佳可能与racc gem有关,因为它可能真的很慢,在这个Github线程中详细说明了这一点
14 req/s vs 60 req/s(我禁用了缓存,而且该应用产生了大量的ActiveRecord对象,这就是为什么数字相当低的原因)使得Rubinius目前不是这个特定Rails应用的好选择。
更新
感谢headius,我修改了基准:
- 显然,我的虚拟机只访问了一个核心(因此Rubinius和JRuby最初的性能很差) - 撞到了四个
- 更新了所有的基准,还加入了JRuby 1.7.9
jruby 99.53 #################################
cruby 59.09 ###################
rubinius 42.63 ##############
从上面的图表中可以清楚地看到,JRuby以令人印象深刻的优势胜出,而且只有一个gem的变化,我认为它应该被推到staging。