Ruby Ruby 性能真的比 Node 差这么多么?

killyfreedom · 2013年08月19日 · 最后由 killyfreedom 回复于 2020年05月02日 · 26968 次阅读
本帖已被管理员设置为精华贴

公司内部有个项目,很简单,几乎没什么业务逻辑,就是根据几个 Key 到 mongodb 中去查询,然后前端返回 JSON

一开始是使用 rails 配合 grape 出的第一版,跑在 passenger 下,然后被 passenger 蛋疼的并发模式伤到了,切到了 rack 配合 grape 跑在 thin 上,快了很多,2 核机器 4 个 process 下,性能提高了 2 倍,但是由于历史原因还是载入整个 rails 环境,而且,Mongodb 使用了 Mongoid 的驱动

看了 robbin 同志关于 api 后台的描述,后面又不满足了,去掉了 rails 环境,单独使用 grape 配合 mongodb 的原生驱动,加入 rainbows 的线程池模式,4 个 process,64 个线程池加上 64 个连接池,大概也就提升了 50%

后面,闲着无聊,觉得 mongodb 的原始驱动是基于阻塞的 socket 模式,在线程池模式下,将会带来大量的线程切换的消耗,因此萌生了基于 Eventmachine 搞一个异步 io 查询的连接池,先尝试了下用 Node 写了个基本的从 Mongod 中查询 api,然后基于 mongodb 的原生驱动写了个基本可用报错不管的异步游标的实现,对比下,顿时,我和我的小伙伴都惊呆了

使用ruby + 自制的异步游标 + rainbows , 单进程大概可以负载 15 个请求每秒,而同样的查询同样的数据源,NODE 单个进程可以负担 50+ 的请求每秒,而且,平均消耗时间都在 ruby 的一半以下

ruby 是 1.9.3--p286,有点老, node 用的是 apt-get 的装的 在 2K 的数据中查询出 113 个数据,数据 JSON 化之后大概有 70+KB,有点大, 但是差距真的感觉十分明显啊

具体代码在: https://github.com/shengxiang205/async_cursor_test.git

node 的项目在 node/query_app

代码写的有点乱,但是结果还是很震惊啊,我一直以为如果用 EM 配合异步 IO 的话,虽然可能会比 node 差一点,但是应该也差的不多,自己测试了下,结果还是不是这样的。。。

不知道是不是我代码有问题。。

内牛满面啊

最新修正 将 113 个数据切换成 1 个数据再次做对比 ruby 大概跑出了400+ req/s, node 跑出了 760 req/s

在接受 @robbin 的提议,加入 GC 参数的设置:

RUBY_HEAP_MIN_SLOTS=600000
RUBY_FREE_MIN=200000
RUBY_GC_MALLOC_LIMIT=60000000
export RUBY_HEAP_MIN_SLOTS RUBY_FREE_MIN RUBY_GC_MALLOC_LIMIT

ruby 大概提升到了 530 req/s, 提升巨大,虽有差距,但是在这种情况下差的也不多了

得出以下结论:

  • 纯 IO 的并发并非是 ruby 的强项,就算是基于 EM 和 Node 依然有差距
  • JSON 序列化的性能上,ruby 的比 Node 差比较多,我这边是基于 JSON(1.7.7) 的序列化库,从 113 个数据到 1 个数据,ruby 追上了 node 不少
  • GC 参数对于这种场景下还是有比较大的影响的

有时间尝试下基于 celluloid-io 做个类似的 demo

V8 就这么牛逼,不服不行……

@blacktulip 是啊,真不明白到底差在哪里。。理论上序列化和反序列化都是跑在 C 的栈上的。。差距还是这么大啊

server 不是 eventmachine 阿,你把 server 也用 eventmachine 这样才有可比性阿

必须的。hidden class+machine code generation 绝配了

@jjym 是的,rainbows 的 backend 切换到了 Eventmachine 模式

@hhuai 传说中优化的没什么好优化的 V8 啊。。。

V8 会深度优化 js 的代码,一些会被 JIT 处理成本机代码进行处理。速度当然是快不少。

@jimrokliu ruby 的 VM 还是不够给力啊。。。虽然写起来很爽,但是,也就像 rails 一样,不能在 ruby 的温床里面不肯出来啊

大概主要瓶颈在 mongo 的 driver 实现上,另外 EM 做的 buffer 拷贝排队工作有点太多...

现在 1.9.3 都 p448 了... 换 ruby 2.1 试试吧?

#1 楼 @blacktulip 自古神评在一楼啊!

其实 V8 在一些嵌入机上没有 mruby 快...

#12 楼 @luikore mruby 现在发展得怎么样了啊?嵌入机不是应该用 C 么

V8 很强大

再强大如果么有配套的工具,实用的应用功能的话,我也不会选择,先前用 cnodejs,后来发现他们的代码库基本不更新了,所以我才转到 ruby-china 来的,学了一个礼拜,基本上可以开始工作了

@luikore 如果瓶颈在 EM 上,那么,基于 Fiber 和 socket 在尝试一个版本是不是效果会比 EM 好?

#16 楼 @killyfreedom 很难说... 用 rack, rainbow, mongo, grape 这个 Gemfile 里已经东西一大堆了吧?如果就一个很素的功能,用 nodejs 或者 go 也可以啊,不用纠结。

另外试过 https://github.com/bcg/em-mongo 了没?

#5 楼 @killyfreedom 我的意思是直接用 EM::Connection 会不会好点?

@luikore EMMongo 不支持 Replica Sets 啊,真是内牛满面,现在这个还不是瓶颈点,真的成为瓶颈点了也只能用 node 写了,功能还是满素的,只是有这么大的差距,不甘心啊....哈

@jjym 就是基于 EM::Connection 写的,

module EMMongoSocket
    BINARY_ENCODING = Encoding.find("binary")

    attr_reader :buff

    def initialize(opt = {})
      @buff    = ''.force_encoding(BINARY_ENCODING)
      @running = false
      @pool    = opt[:pool]
      @proc    = nil
    end

    def post_init
    end

    def add_processor(proc)
      @proc = proc

      unless @buff.empty?
        EM.next_tick { @proc.call(@buff) if @proc }
      end
    end

    def remove_processor(proc)
      @proc = nil if @proc == proc
    end

    def checkin
      @pool.checkin(self)
    end

    def receive_data(data)
      if data
        @buff << data

        unless @buff.empty?
          @proc.call(@buff) if @proc
        end
      end
    end

    def unbind

    end
  end

楼主可以试试 sinatra-synchrony + em-mongo 搭配 thin -s 4 . 我做的一个简单的 insert 测试 https://github.com/nickelchen/sinatra-synchrony-template 在这个基础上再加上 em-synchrony/em-mongo 效果更好。

用了不同的 gem 肯定会有一些是阻塞 IO 的,即使使用 EM,你的代码里是阻塞的,那最后还是那样。Node 的话,估计他的插件本身都是异步 IO 的,所以这样比较还是不公平的。我觉得问题不在 EM 啊!

@kenshin54 这个我知道呢,所以,我基于 EM 的 Reactor 机制重写了通讯的 socket,从某种角度上来说和 EMMongo 是类似的

@nickelchen 我看了下你的测试数据,大致对比了下单核性能,如果只查询一条数据,我在 Intel(R) Pentium(R) Dual CPU E2220 @ 2.40GHz的机器上大概可以做到 400 个请求每秒,单进程,但是 Node 可以做到 700+ 的请求每秒,我觉得你可以尝试下用 node 做一个同样功能的版本,我相信性能数据会让你吃惊的

呵呵。。。

https://github.com/fredwu/spiky_xml

ruby parsing 27000ms
ruby concurrent parsing 27000ms
jruby parsing 19000ms
jruby concurrent parsing 11000ms
nodejs xml2js parsing: 27616ms
nodejs libxmljs parsing: 1333ms

这数据对比还好吧。node 的强项就是 IO 并发,做这种测试其实就是测 IO 并发,你的 node 性能是 ruby 的 3 倍,说明 ruby 已经很不错啦。而 ruby 本身在 IO 方面就算传统弱项,这个差距在我看来真的算很小了。

另外你如果调整一下 ruby 的 GC 参数,还能够提高性能 50-100%。

@fredwu 果然差距巨大啊

@robbin 我实测的时候,内存峰值不到 60M,这个时候 GC 影响大么?这个真没什么经验..

自己尝试了下,用了你 robbin_site 下的 GC 参数,在只读取 1 个数据的情况下,从 400 req/s 提升到了 530 req/s

读取 113 个数据的情况下,从 17 req/s 提升到了 25req/s每秒

果然,GC 影响这么大...

#20 楼 @killyfreedom :😓: 我说的是 server...

@jjym 😄 rainbows 的 server 就是基于 EM 的呀

#23 楼 @killyfreedom mongodb 我没测试过,但是我测试过 memcached 在 EM 和 Node 的性能,用的 EM 自己提供的 memcached 协议实现,Node 用的是 https://github.com/3rd-Eden/node-memcached ,结果是 EM 快,不包含 web 环境。

#32 楼 @killyfreedom 额。。。结果是 EM 快。

有 bug 啊 @huacnlee

#33 楼 @kenshin54 这个地方 URL 前后需要加个空格就行了

#34 楼 @ywjno 不是这个问题,是我写的内容和显示的最后就不一样

我在用 ngx_lua 提供 json api, 性能比 node.js 还猛。

V8 引擎 很强,ruby2.0 虽然比 1.9 快,但远没有 V8 强。

@wxianfeng 尝试关注下这个,但是和 NGINX 整合在一起应该只适合很素的需求吧 @sevk ruby 2.0 感觉和 1.9 比不是颠覆性的升级,性能感觉提升不是特别明显

matz 自己也说过 JavaScript 的虚拟机做了大量优化,比 Ruby 的 VM 快多了。

@lifuzho 是啊...比不过人家啊

其实我蛮搞不懂的,如果有人愿意用 nodejs 这种 bt 的语法去写 web 相关的东西,为什么不直接用 c?

@rasefon EM 的语法其实和 nodejs 差不多,都很折磨人....c 可比 node 烦多了...

#41 楼 @rasefon js 现成的轮子随地捡,C/C++ 的话你不造一屋子轮子都不好意思出门...

#36 楼 @wxianfeng 可以试试:nginx+mruby 的组合。

v8 的性能是没有其他脚本实现可以媲美的

#45 楼 @ouyang mruby 可以用了?可以试一下 nginx+lua

#47 楼 @search 有人模仿 nginx lua 写了个 mruby 的 nginx 模块,性能不错。

@kenshin54 我仔细看看,你有测过 n 100 c 1000 的情况不

#50 楼 @killyfreedom 结果还是 Node 快,你这个测法不对把,1000 个并发,100 个请求?

#50 楼 @killyfreedom 我又更新了一下,之前是用 1.9.3 的,现在用 2.0.0 测试了一下,跟 Node 比较接近了

@kenshin54 说错了,我 100 个并发,1000 个请求,em-mongo 不带连接池的吧

我写了个类似的,有一个 256 的连接池,还是基于我原来的异步游标 纯性能大概到了 1300req/s

你的没有 JSON 序列化,我的机器上大概跑出了 1500req/s

已经比基于 express 的快了

#encoding: utf-8
lib_path = File.dirname(File.expand_path(__FILE__)) + '/lib'
$:.unshift lib_path

require 'rubygems'
require 'mongo'
require 'json'
require 'eventmachine'

host             = %w{ 192.168.10.223 27017 }
$mongo_connection = Mongo::MongoClient.new(host[0], host[1], :pool_size => 64)
$db               = $mongo_connection['data']

require 'async'

Async.host = '192.168.10.223'
Async.port = '27017'


class Server < EventMachine::Connection

  def receive_data(data)
    # $collection.find(:creator => '[email protected]', :as => 'task_state_log').limit(1).each do |doc|
    #   send_data doc
    #   close_connection_after_writing
    # end

    cursor = $db['data'].find(
        { :creator => '[email protected]', :as => 'task_state_log' }
    ).limit(1)

    # puts Thread.list.inspect

    Async::AsyncCursor.new(cursor: cursor).to_array do |data|
      send_data JSON.dump(data)
      close_connection_after_writing
    end
  end
end

EM.run do
  EM.next_tick do
  end

  EM.start_server '0.0.0.0', 4001, Server
end

#53 楼 @killyfreedom em-mongo 不带连接池,但是 em-synchrony 有一个可以创建 Pool 的东西。

你可以在 ruby 2.0 下跑跑看我的代码,我跑下来速度不差。

@kenshin54 你的 ruby 2.0.0 提升好大啊 你的代码我换到 ruby-2.0.0-p247 下大概也就跑到了 1600req/s, 远没有你提升这么巨大.... 尝试把你的结果 JSON 化出来看看

#55 楼 @killyfreedom 加了 JSON.dump 还是有 2400 左右

@kenshin54 果然,性能差距体现在 rack 和 rainbows 上面啊,冤枉 EM 了啊

@kenshin54 这个场景用 c 100 n 1000 来测似乎到不了 CPU 的极限啊,用 c 200 n 10000 测试,数据就稳定多了

#58 楼 @killyfreedom c 100 n 1000 比 c 10 n 1000 还强 可以到 2700+

@kenshin54 是啊,因为 c10 并发对于纯 socket 的 server 来说负载太低了

#57 楼 @killyfreedom ...我就说 server 也直接用 EM::Connection

#60 楼 @killyfreedom 貌似 EM 默认用的 select,你可以换成 EM.epoll 或者 EM.kqueue 再试试

@jjym 现在才终于理解了你的意思..啊,我真是太 2 了...

@kenshin54 EM 1.0 默认是开启 epoll 的

@kenshin54 你们有没有用 Goliath + Nginx 测试过。用 Nginx 做反向代理,配置负载均衡,后端根据需要启动多个 Goliath sever 进程。Goliath 就是专为这种场景设计的,内部使用了 EM。并且非常稳定,我们的服务在生产环境上,从 2013.4 月份启动到现在 就没出现过问题,也没重启过。

#65 楼 @outman 没测试 我测试的是 EM 和 Node 的比较,前面都不搞 Nginx 的,都单进程。Goliath 在 2.0 下和 Node + Express 差不多

@kenshin54 哪这样单纯的比较意义是啥么呢?说明 nodejs 比 ruby 更牛逼?我貌似应该问@killyfreedom 楼主的。

@outman 纯粹探索在单进程模型下,同样 EM 的实现,ruby 和 node 有多少差距,以此来推断如果逻辑很素的但是性能要求很高的服务比较适合哪个体系来做

最初的目的是同样的 api 服务器,基于 rack 的 em 的 ruby 和基于 express 的 node 的差距比较大,所以比较好奇,差距在哪里

Fiber 作为编程很友好的并发形式,其实我个人还是很喜欢

@killyfreedom 哦,了解了。其实用 nodejs 来做这种业务我还没用过,以前只是用来做一些前端的消息 PUSH。nodejs 经过几年的发展,现在应该很强了吧。

@outman 我也是尝试了解下 nodejs,感觉应该发展的不错,但是不尝试么又不知道,就自己写点代码摸索摸索,对比对比,就出了这次的事情,哈哈

@killyfreedom 不错,不错。谢谢分享

楼主,继续给你优化建议哈:

ruby 的 json gem 性能其实是比较一般的,建议你换成 oj,估计还能整体提升 10% 的性能。

另外,如果你不怕麻烦的时候,可以到 github 上找找 ruby2.0 的性能补丁,反复测试测试各种参数各种补丁,运气好的话,还能提升 30%。

不过 ruby 真的没有必要和 node 比较。node 那种嵌套回调不适合写复杂逻辑,非常反人性,两者应用场景有差别。个人看法:n 年以后,Go 会消灭 node,不信等着看。

几乎没什么业务逻辑,就是根据几个 Key 到 mongodb 中去查询,然后前端返回 JSON

这么素的业务逻辑可不可以把相关要查的 key 全部丢到 route 里面,然后查询结果直接生成静态 cache 丢给 nginx,性能也许还能提高个十几倍呢……

#72 楼 @robbin 为什么是 Go 不是 Er 呢?

@robbin 同意这个观点,基于回调的写法真的太反人类了,再挖掘性能就等日后有兴趣再说了,毕竟现在还没有这么大的性能需求

不过还是挺有收获的,自己这么摸索几下

感谢@kenshin54 后面配合做的测试,理解也深刻了许多啊

#72 楼 @robbin node 社区在处理回调那种编程结构方面积累了很多模式和框架,有一次 ruby tuesday 上就说了这个话题,建议了解一下,虽然不敢说 nodejs 一定能成功,但是起码这块比我之前想象的要好很多的

#76 楼 @fsword node.js 要是真好用的话,那当年开发 Twisted 的一定是脑残,RMS 一定是弱智

celluloid-io 弱爆了,还不如 EM....估计相比 nodejs 和 EM 更多人宁愿用 goliath

#76 楼 @fsword 相信要不了多久 Node.js 会出现类似 goliath 的用同步风格写异步代码的方式。

#79 楼 @ashchan node 社区有 node-fibers 这种开源项目,把异步回调封装成 fiber 的。但是 node 社区整体的编程风格就是回调,无法被改变。

#76 楼 @fsword 我钻研过一段时间,无论怎么简化,没有改变编程逻辑上复杂度的上升。编程语言的发展历史,就是越来越抽象,表达能力越来越强。所以 node 的发展方向有问题。

#74 楼 @blacktulip 因为 Erlang 小众,不够普及。

#80 楼 @robbin 同意。也许是因为自身才疏学浅,在参与几个 node 项目过程中,感觉代码逻辑不直观,难以抽取可重用的代码块,每写一段逻辑都要去翻看已经存在的代码才能确认到底要怎么写。

#81 楼 @robbin 用 coffee 可以有一定程度的改善,以前还有点看不起这小玩意,真正写起来,有质的提升。碍于 js 本身的问题,所以我现在写起来是小心翼翼,但如果做小的业务服务端,node 还是首选,利大于弊。

#82 楼 @robbin V8 已经支持了 Generators 特性,现在还没有默认开启。 看看这个 https://github.com/visionmedia/co ,异步嵌套问题不再是恶魔

#80 楼 @robbin

Ruby 也是这样做的。这里有两篇文章。 http://www.igvita.com/2009/05/13/fibers-cooperative-scheduling-in-ruby/ http://www.igvita.com/2010/03/22/untangling-evented-code-with-ruby-fibers/

这文章作者有写几个 gem, 主要用途也是以同步的来执行异步调用。

我和楼上诸位观点略有差异,认为 node.js 未来发展肯定会比 go/Er 好(毕竟前端 browser 人家是唯一语言,优势太大。。),.v11.2 的 yield 出了以后,javascript 这边应该是也有了 ruby 的 yield 能力(也就是 85 楼的 generator),然后 javascript 允许 function 作为 first class 处理,函数返回函数,这个能力似乎在异步编程里面很有用,诸如promise的异步编程手法也不能说完全反人类,只是比较新的想法,目前大家都一时不太能转过弯来吧。。

#87 楼 @ericguo Promise 好像还不如 Twisted 自带的 Deferred,Twisted 自带的 Deferred 的 callback 好歹是能返回另外一个 Deferred。 现在都没什么人用 Twisted 了,都去用 Erlang 了。

刚好我做了两个基本同样的网站,分别用 ruby 和 nodejs,大家可以比较一下看: ruby: http://xmzdm.com nodejs: http://wjzdm.com 说明下,ruby 的是 sinatra+sqlite+thin,nodejs 的是 express+mongodb+nginx 话说,只要不用 ror 框架,sinatra 的性能也是非常不错的。

#88 楼 @bhuztez promise 的 resolution 中也可以嵌套另一个 promise,至于返回,作为 javascript,本身就能返回函数。deferred 手法不熟不敢说啥,不过 js 里面也是有的,只是现在 promise 更主流,Python 的 Twisted 或者 Erlang 我都没经验,所以也不好说啥。

#90 楼 @ericguo promise 和 deferred 是同一个东西好不好

EventMachine

require "eventmachine"

class Echo < EventMachine::Connection

  def receive_data(data)
    send_data("Hello World")
    close_connection_after_writing
  end
end

EventMachine.run do
  EventMachine.start_server('0.0.0.0', 10000, Echo)
end

nodejs

var http = require('http');
http.createServer(function (req, res) {
  res.end('Hello World');
}).listen(10000, '127.0.0.1');

ab -n 6000 -n 66 http://localhost:10000/

EventMachine:261.55 rps nodejs : 254.77 rps

#91 楼 @bhuztez promise 和 deferred 还是略有差异吧。。我也刚开始学,老外就是会起名啊!

web 这东西谁好谁劣,真心不是语言执行速度决定的。
规模小了看 io 模型,规模大了看架构设计。

未来发展的话个人看好 node 1,群众基础好,大家不见国内推动 nodejs 发展的主要来自前端的兄弟么,后端当然更不用说。 2,商业支持好啊 目前 joyent 打前锋,微软 axure 也想插一杠子。 3,rails/ruby 社区为 nodejs 打下坚实基础了,不见 n 多 nodejs 框架工具都是 ror 翻译过去,n 多大牛也转向 node 社区 4,游戏行业之 nodejs 主要采用者,做 tcp,二进制,高并发的好东西。

io 并发的性能当然还是 node 胜出,问题是,我喜欢 ruby 的优雅啊。 另外:w=p*t:)

zzz6519003 回复

这么多年啦... 好久没关注了

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