性能快报:Heap Stacks 提升 1.8.x 线程性能 & MacRuby AOT & ZenProfile 和 EventHooks

阅读数:290 2009 年 5 月 27 日

话题:Ruby语言 & 开发架构

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.61.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