Ruby JIT for MRI 开始开发了

ceclinux-github · 发布于 2018年02月08日 · 最后由 jakit 回复于 2018年02月12日 · 2075 次阅读

来自这个commit: https://github.com/ruby/ruby/commit/ed935aa5be0e5e6b8d53c3e7d76a9ce395dfa18b, 已经有人迫不及待的开玩了: https://www.johnhawthorn.com/2018/02/playing-with-ruby-jit-mjit/

感想:

  1. JIT开发依然在非常初级的阶段,很容易写出奇怪的代码来跳过(或触发JIT优化)
  2. 性能提升巨大,我的机子上跑2.6.0-dev --jit 比2.5.0快了差不多一倍,非常期待后续提升
共收到 26 条回复

怎么我看的对比数据,说的是目前提升还不是很明显呢

huacnlee 回复

我这按它的脚本,是这样的

 ceclinux@ceclinux-pc  ~/ruby  973   trunk ?  time ./ruby --disable-gems --jit --jit-wait --jit-verbose=1 15a.rb                                                   ✔  10248  18:46:44 
JIT success (55.3ms): block in <main>@15a.rb:13 -> /tmp/_ruby_mjit_p2514u0.c
JIT success (183.4ms): calculate@15a.rb:1 -> /tmp/_ruby_mjit_p2514u1.c
{:result=>600}
Successful MJIT finish
./ruby --disable-gems --jit --jit-wait --jit-verbose=1 15a.rb  4.84s user 0.03s system 99% cpu 4.880 total


 ceclinux@ceclinux-pc  ~/ruby  972   trunk ?  time ./ruby --disable-gems  15a.rb                                                                                 1 ↵  10250  18:47:17 
{:result=>600}
./ruby --disable-gems 15a.rb  9.08s user 0.00s system 99% cpu 9.087 total

一倍差距算是很大了把?

对 Rails 应用改进不大,他们还在找原因,估计算是 6 的工作了

rails都卡在io了。。。。

known issues: Performance is decreased when Google Chrome is running。这就很搞笑了

pynix 回复

Rails 的类过多的方法也阻碍了 JIT 的优化空间,具体需要 @luikore 来讲解下,推上 discourse 的人在跟 kokubun 讨论 jit-friendly 的策略,见 https://twitter.com/k0kubun/status/960112559343878144 这 thread

可以說是腳踩 Graal,吊打 JRuby,3x3 目標立刻實現。三倍是什麼?提升六倍也可以啊。

其實那個 Benchmark, Nv 和 CSeaton 已經回覆了,如果讓 Graal / JVM Warm Up, 不計算一開始的 Startup 時間,TruffleRuby 還是快最快的 MJIT 3 倍左右。

理論上那個是比較舊版的 TruffleRuby, 新版本和剛剛才開源的 SubstrateVM 會更加快。當然, Trade off 是 Memory Usage。

很可惜整個 Ruby 社區對 Java / JVM / Oracle 很有保留。

ksec 回复

JRuby 主要是和 MRI 有诸多不兼容的地方。一方面没有引入类似 JNI 的机制做 C 扩展的引用,另一方面像是 Ruby 这几个版本非常重要的 Fiber 特性,一直不跟进。这对 JRuby 的推广有很大的障碍。但是反过来说,Ruby 社区为了保留灵活的演进,在几次劝进下都拒绝搞标准化。事实上最后搞了一个标准化 Ruby ISO,基于那个实现了 mruby,社区反映也是不温不火,两边都有苦难言啊。

dsh0416 回复

仔细看了两遍,回答的很用心,信息量很大,感谢🎉

dsh0416 回复

话说这个实现也糙了一点。。。把 hot block 找出来然后 C 化然后 compile 而且还放在 /tmp。重点是,这样一来就依赖 gcc / clang 了。

我期待的好像不是这样的,有点失望。我期待它能本身提供编译的功能,就是 ruby file.rb 就已经能从 YARV -> 机器码,而不依赖外部工具。不过,实现到那样的 level,估计 Rubinius 会便利一些,后端天然的有 LLVM 垫背。

如果 MJIT 实现了即时 YARV -> Native Code 即时存储到内存空间预备即时调用,我觉得它干脆做成下一个 LLVM 了。

pynix 回复

刚好相反,rails 太臃肿了,IO 开销实际上是很小的一部分,服务器真正做的部分只有通过调度、读request、写respose。

而你的 HTTP parsing、业务处理( rails 本身有大量的对象映射、抽象、创建和销毁等等)才是最大的问题。这些属于计算密集问题。

mjit 在类型明确一些的 block 是更容易被 jit 编译的,但像 AR(Active Record) 则不然,一层层的抽象几乎不尽相同的数据,动态的元编程动态嵌入方法和属性,阻碍了 old gen code 的形成。

jakit 回复

动态的元编程动态嵌入方法和属性,阻碍了 old gen code 的形成。

這個正正是 Truffle 的強項。而且我才剛發現, TruffleRuby 已經支援 Nokogiri 及 OpenSSL。距離能夠真正運行 Rails 日子不遠,十分期待。

jakit 回复

我期待它能本身提供编译的功能,就是 ruby file.rb 就已经能从 YARV -> 机器码,而不依赖外部工具。

这不叫 JIT 这叫 AOT。尝试的话 RubyMotion 的 Ruby 就是 AOT 编译的。这些尝试 Ruby 社区都有做过,优缺点也都是很明显的。

rails 太臃肿了,I/O 开销实际上是很小的一部分... 而你的 HTTP parsing、业务处理才是最大的问题。

我们拿比 Rails 轻量很多的 Sinatra 和 mysql2 裸驱动,不用 ActiveRecord 出来跑个 benchmark 好了。

Web Server Basic View View + DB
WEBrick 273 req/s 116 req/s 111 req/s
Thin 1597 req/s 174 req/s 139 req/s
Unicorn 605 req/s 121 req/s 121 req/s

为什么 Thin 在 Basic 的时候碾压其他 Server,因为它 I/O 异步了。为什么 View 和 DB 又变差了呢?因为读取模板和查询数据库是同步的。我写 midori 的时候,把每个请求的栈分配都弄到极轻量,还用了 C extension 做 HTTP Parser,单跑 Hello World 的时候也就比 Thin 再快一倍,而加任何一个 I/O 操作就慢十倍。

谁是「很小的一部分」谁是「很大的一部分」就是这么简单的问题,哪来什么刚好相反。但是只关注 I/O 会限制 Ruby 在 Web 服务器以外别的领域的发展。所以 MJIT 还是非常必要搞的,没人讨厌快,但不希望快是以牺牲其他东西为代价,比如 Rubinius 把代码写到自己也维护不动。而 MJIT 所做的就是这一点。

dsh0416 回复

为什么 Thin 在 Basic 的时候碾压其他 Server,因为它 I/O 异步了。为什么 View 和 DB 又变差了呢?因为读取模板和查询数据库是同步的。

你给出来的数据这很明显,Web 框架帮你找到路径之后调用你的业务逻辑肯定会有 View 渲染和 DB 调用,你看看 View 和 View + DB 的差异并不大,说明 DB IO 效率高了去了。

服务器做的事情,是把 数据 read,然后交给单个 task 线程去处理对应的路径反射你的Controller渲染你的 View(太长不写)。你看没有 View 效率多高,最差的情况拖了 6x。让一个性能很好的 Thin 拖得只剩下跟其它一样的水平。

异步不异步是另外的一个问题,服务器本身 schedule socket queue 是可以异步的。

但是你一个 Controller 作的事情比如你写了:

  1. 处理 DB
  2. 计算数据,暂且把数据搞了个斐波那契
  3. 处理 View

难不成你把 Controller 这三件事情分别异步成三个线程,甚至然后做完了再通知去让服务器 response 吗?像移动客户端有些 lib 设置了超时,等你异步做完这些事情,再write我客户端估计都已经 close 了。

先不讨论异步,Webrick 的 parse 是 Ruby 写的,比如 WEBrick::HTTPUtils::FormData

拿 Ruby 讨论 IO 就是耍流氓

如果你说用 Go / C++ 在流处理的时候碰到瓶颈,还是值得讨论的。

jakit 回复

异步提升的是并发量,不是减少平均响应时间,不要混为一谈。

jakit 回复

你是不是觉得从数据库连接读取数据的时间算做 IO 开销,等待数据库处理的时间不算做 IO 开销?

Rei 回复

你误解了我的意思。。。我说的不是 Web 响应时间,我说的是网络通讯

Rei 回复

等待数据库处理的时间是数据库本身的问题了,不属于 Ruby 管的范围,你们写 Rails 难不成:

class xxxController
  def xxx
    @data
    Thread.new { // 数据库 -> @data }
  end
end

你确定 View 能渲染出 @data 的内容,你确定渲染的时候 数据库已经读完了?

好,如果你 join 是吧,你可以让这次 puma task processor 的线程等待你执行完毕,但是,这就同步了呀

难不成在一次 html page request (Socket connection)中(也就是非 Ajax),你能告诉我怎么能先 write html content 给浏览器(就是 View)再等数据库异步回调再 write 数据的方式吗?

jakit 回复

所以说不要把问题混为一谈,数据库查询的耗时不能用应用层的语言解决,能做的就是不要让 CPU 干等着,转去做别的任务。目前已有的 Thread 已经能一定程度解决问题,但是 Thread 切换支出大、共享内存容易出 bug,并发性能比 go node 等有很大差距,所以在研究引入新的并发模型。

这些问题不是 MJIT 对应的,MJIT 想解决的是运算性能,如果你的应用是计算密集,并且没有多少 IO 开销(例如查数据库),那么可以从 MJIT 获益良多。但是一般 web 应用 IO 占到一半左右,从 MJIT 获益多少要实测才知道。更快的执行速度当然是更好的,例如序列化速度可以加快,这对一些结构复杂的 API 服务很有用。

Rei 回复

其实我的回复只是告诉 #7 其实 rails 并没有被 IO 卡,puma 的异步处理还是很优秀的。

希望 mjit 能在 rails 这块庞大的 monolith 发挥作用,随着 SPA 开发模型、前端独立岗位的出现,还是亟需的。

jakit 回复

「让一个性能很好的 Thin 拖得只剩下跟其它一样的水平」少开玩笑了,Thin 的优化可以说是非常差了,和其他 Web 服务器比起来最大的区别就是把 I/O 包装成了异步。

puma 只处理 Web 服务器的 I/O,应用层调用 DB 也是 I/O,其背后的调用逻辑和

Thread.new { // 数据库 -> @data }

还真的区别不大,Rails 的实现就是类似于 GCD 一样做了个线程池,很明显这个方法是问题的根源。

你确定 View 能渲染出 @data 的内容,你确定渲染的时候 数据库已经读完了?

当然是可以的,但是这里就不是依靠 Thread 或者什么 join 来解决的问题。这就是并发和并行最大的区别,吞吐量和请求处理时间也不是一个概念。你试试用 em-synchrony 替换掉这个部分,就会得到数倍的性能改善。em-synchrony 也是纯 Ruby 实现的,按理说,往里面加一层东西应该会更慢才对,为什么反而快了好几倍呢?

你对并发和并行的理解太混淆了,以及对 I/O 是什么理解很有问题。View 层和 DB 层都不是在 Ruby 里面处理的,为什么同样调用 mysql2 的驱动,用 goroutine 和用 Ruby 性能差别这么多,问题就出在这里,调用的方法不对。而不是什么别的。

dsh0416 回复

我又不打算混在一起讨论~

huacnlee 分析 2017 年的 RubyGems 统计数据 中提及了此贴 02月21日 11:15
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册