Ruby 1.9 把 Ruby 世界从 1.8.x 的用户空间线程(userspace thread)系统带入原生线程(native thread)。尽管全局 VM 锁(Global VM Lock,VM)仍然影响着 1.9 的原生线程,使得每次只有一个 Ruby 线程能被执行,但是向原生线程的转变也带来了其它的好处。
Joe Damato 对 Ruby 1.8.x 的线程实现中的一个问题进行了研究,在 1.9 中由于原生线程的引入,该问题不复存在。简单地说:由于线程的上下文切换会导致线程栈内容的完全复制,如(针对挂起线程)从栈到堆的方向,还有针对被调度线程的另一方向,这样做开销非常巨大。大凡栈分配得大或者带有巨大栈帧(stack frame)的应用程序,都难拒其扰。
通过维持多个栈并在这些栈之间进行切换,原生的线程实现就不用担心出现这种低效率情况的尴尬了。Joe 的文章详细描述了他的“heap stacks”方案,并把这套方案带到了 Ruby 1.8.x 上。
如此一来,结果非常明显地体现在了性能的提升上──2 至 10 倍的性能改善,这使得基准测试的结果一举逼近 1.9.1。
从 GitHub 上我们可以找到给 1.8.6 和 1.8.7 打过补丁的代码。
Heap Stacks 方案是为了根除 Ruby 1.8.x 中最大的低效率问题的又一次尝试,除此之外,另一个就是解决了部分长期困扰 Continuation 和 GC 方面问题的 MBARI 补丁。
条条大路通罗马,要让 Ruby 实现更高的性能,别的路也行得通:就在不久前,MacRuby 项目开始了实现基于 LLVM 的 VM 的工作。现在这项工作已经有一部分被用来为 Ruby 创建一个提前编译(Ahead Of Time,AOT) 的编译器了。在这里,AOT 是和 JIT(Just In Time,实时)编译器相对应的──也就是说,AOT 编译器的运行,并非在运行期编译,而是从源码生成出可执行文件:
表达式会被编译成 LLVM IR,然后转换成位码(bitcode),再变成汇编语句,最后变成机器码。这是真正的编译 :-)
这样做有利于 1) 代码混淆,及 2) 在动态代码生成不允许的环境下使用 Ruby。
最后,剖析器(profiler)可以用作来发现应用程序瓶颈的一种手段。Ryan Davis更新了他的 zenprofile 剖析器。该剖析器使用 Ruby 运行时的事件钩子作为跟踪方法调用的有效方式。说起来 Zenprofile 也颇有一段历史了,不过目前更新的版本依赖于 event_hook ──这个 gem 把建立钩子所须的原生代码单独抽取了出来。藉由 event_hook,我们可以不必大费周章地通过编写原生代码给 Ruby 解释器挂上钩子,现在只需编写纯 Ruby 代码就可以实现事件钩子了。Zenprofile 使用了 event_hook,提供了一套剖析逻辑的纯 Ruby 版本,以及使用了 RubyInline 和 C 作为原生代码的一套更快的版本。
简单看一下 zenprofile 的代码,我们可以发现使用 event_hook 非常简单,只需要扩展EventHook
类,然后覆盖某些方法,如def self.process event, obj, method, klass
,就可以捕获事件了。
Zenprofile 还提供了可用于关注个别方法性能的spy_on
功能。该功能可通过 Ruby 代码进行配置,如要关注Integer#downto
的性能,下面是一段来自misc/factorial.rb
的例子:
require 'spy_on' Integer.spy_on :downto
查看英文原文: Performance Roundup: Heap Stacks Boost Threads in 1.8.x, MacRuby AOT, ZenProfile and EventHooks
评论