BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Rubinius内部细节:线程、对象空间和调试

| 作者 Werner Schuster 关注 9 他的粉丝 ,译者 高昂 关注 1 他的粉丝 发布于 2007年8月7日. 估计阅读时间: 8 分钟 | ArchSummit北京2018 共同探讨机器学习、信息安全、微服务治理的关键点

作为Evan Phoenix谈Rubinius:虚拟机内幕面面观的续篇,本文的第二部分将更加深入细节的实现。

Ruby 1.8.x目前使用用户空间的线程,意味着它无法充分利用多核技术带来的便利,因为操作系统只能感知确定一个线程。Rubinius目前使用用户空间线程,但是Evan同时也考虑了其它的解决方案:

对于Rubinius来说,在相同的地址空间中实现多重解释器是件价值不高的事情。编写整个虚拟机的时候就注意到了本地线程安全和可重入机制。这一切来自我在Sydney项目的工作经验,这个项目是对Ruby 1.8.2版的整理。

你可以轻松地创建两个Machine(基础的数据结构)并同时将其初始化。它们随后都会保持完全的独立性。唯一要做的工作就是确保两个Machine可以合理地调度它们的代码。你甚至可以在两个不同的本地线程中启动两个虚拟机实例并且使其通过通道进行相互通信,这将给你带来一个真正可以利用多处理器特性的Ruby。

在标准的Ruby发布版中,包含简单的调试器,它使用跟踪特性来实现。我们可以通过设置回调函数的方法来使用这个调试器,而这个回调函数在每行新的代码被执行之前被调用。回调函数通过set_trace_func方法来进行注册。(这种方式同样也用于概要分析)。这种方法带来的问题是它较高的系统开销。目前Ruby代码每执行一行,意味着要调用跟踪功能并且需要确定是否在这一点上延迟执行。另外也存在其它使用本体扩展的解决方案,就像ruby-debug或者Visual Studio 2005的Ruby in Steel插件所包含的Cylon调试器一样。

类似于Ruby.NET、IronRuby或是XRuby的Ruby编译器,恰好把调试信息生成在目标IL或字节码中,并且使用各自虚拟机的调试特性。

Rubinius最近也获得了代码调试的支持,实现了断点的功能,它只有在某个断点命中的时候才会引入额外的性能开销。Evan对于功能实现这样解释道:

我已经实现了基本的调试设施,这为我们带来了全速的断点(Full Speed Breakpoints)。全速断点意味着在运行期我们不会因为使用调试器而带来任何性能上的损耗了。我认为这是一次巨大的胜利,因为我听说Ruby调试器的速度一直以来是个瓶颈。我已经开始在FSB的基础之上建立更高级层次的功能,最终,我将可能将其写入类似于ruby-debug的机制当中,或是至少在感觉上是类似于ruby-debug的机制之中。

从技术角度说,FSB通过字节码替换进行工作。当断点设置之后,系统使用反射机制查找精确的CompiledMethod对象,在这里需要设置调试断点。随后,系统计算字节码,判断断点需要在何处发生,并且使用叫做yield_debugger的奇妙的方式替代目前的指令。当命中这条指令时,系统将控制权交给调试器,这个调试器附属于当前运行的线程上。当这个方法需要继续执行时,旧的指令又被交换回来并且指令指针的值回退1,并被重新激活。

这个功能运行得很好,因为调试器只是设置为空闲状态,等待运行实际代码的线程与之建立联系。这个功能运行得很好的另外一个原因是Rubinius中的方法上下文(Method Contexts)是被作为一等公民对待的。方法上下文和栈框架(Stack Frame)其实是一回事,它描述了方法运行的状态。在Rubinius中,你可以从虚拟机中获取系统中任何一个状态的方法上下文。随后就可以通过检查那个对象来发现此刻具体发生的事情。得到方法上下文最简单的方法就是调用“MethodContext.current”,将返回当前运行方法的方法上下文。

随着例如JVM和CLR这样Ruby托管运行时方式的实现,Ruby的ObjectSpace特性已经出现了一点问题。ObjectSpace允许对Ruby堆中所有可触及的对象进行访问,比方说:

ObjectSpace::each_object(Class){|x|
p x
}

上述代码将打印当前Ruby软件栈中的所有Class对象。

JRuby的开发者Ola Bini最近撰写了关于ObjectSpace在JRuby环境下的性能影响一文。因为JVM(或者是CLR)不允许直接访问堆,必须跟踪每一个对象的创建并且保留所有活动对象的清单。Evan对于rubinius中ObjectSpace的情况,这样解释道:

我们实际上目前还没有实现它。我们将不会像JRuby实现那样有这么多的麻烦,因为我们具有对象存储单元的直接访问能力。

Smalltalk语言实现这样的行为主要使用被称作next_object的单一primitive实现。当你在大多数对象中调用next_object时,将返回紧随它们之后的一个对象。目前,之后的定义是与具体实现相关的,但是通常的定义是在内存中当前对象的下一个对象。

需要注意到这种方式在任何情况下都不可能保证是无损或者精确的,并且,为了以一种不困扰使用这套接口的开发人员的方式完成,这种方式仍然会有一些性能上的额外开支。这可能令人困惑,因为虚拟机(包括Rubinius)依赖于这样一个事实:它们可以在内存中重新整理对象,而不会使已经运行的代码产生任何问题。

实际上Rubinius这样做是很频繁的,因为垃圾回收器中有一种就是一个复制-压缩收集器,因此较新的对象要经常移动。如果你在一个对象上调用next_object,方法返回对象B,你绝不能指望在下次调用next_object的时候,返回的还是对象B。

与之相关的一件事是Rubinius目前已经完成了将对象的object_id和它在内存中的位置解耦。MRI的object_id返回对象在内存中的地址,这样之后,我们就可以通过调用ObjectSpace._id2ref,以object_id返回的数值作为参数,从而返回相应对象。在MRI中,_id2ref要实现起来相当容易,因为它只需直接从内存的相应位置取得对象就可以了。但是经常用到_id2ref的人知道,有时候你取出来的对象和原先放入的完全不同。这是因为旧的对象已经完成生命周期,而新的对象已经被分配在同一个地方。这虽然没有妨碍人们继续使用_id2ref,但人们应当清楚,_id2ref并不是像人们想象的那样工作的。

无论如何,Rubinius目前还不支持_id2ref,因为object_id保存的并不是内存地址。我们会找到支持的方式的,但和JRuby一样,这可能会成为一个彻底的额外性能开支。

查看英文原文:Evan Phoenix on Rubinius - VM Internals Interview

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT