深入了解 Ruby 实现:MRI、YARV、JRuby 与 Rubinius
1. 从 MRI 代码看端倪
在 Ruby 的实现中,MRI(Matz’s Ruby Interpreter)是最初基于 C 语言的实现。通过分析以下代码,我们能发现一些关于 MRI 的有趣细节:
if (obj1 == obj2) return Qtrue;
return Qfalse;
在这段代码里,
rb_obj_equal
的两个参数在 C 代码中都被声明为
VALUE
类型。在 MRI 内部,当引用一个对象时,使用的就是
VALUE
。同时,很容易推断出
Qtrue
和
Qfalse
分别是 Ruby 中
true
和
false
对象的 C 语言版本。
再看
Array
的
map!
方法(也叫
collect!
)的实现代码:
static VALUE
rb_ary_collect_bang(ary)
VALUE ary;
{
long i;
rb_ary_modify(ary);
for (i = 0; i < RARRAY(ary)->len; i++) {
rb_ary_store(ary, i, rb_yield(RARRAY(ary)->ptr[i]));
}
return ary;
}
即便不是 C 语言高手,也能轻松理解其逻辑:通过
for
循环遍历数组,对每个元素调用代码块(使用
rb_yield
),并将每个元素替换为代码块的返回值(通过
rb_ary_store
)。
2. YARV:带字节码加速的 MRI
YARV(Yet Another Ruby VM)是 MRI 的下一代版本。下载并解压 YARV 后,会发现它的代码结构与 MRI 有相似之处。例如,
map!/collect!
方法在 YARV 中的实现如下:
static VALUE
rb_ary_collect_bang(VALUE ary)
{
long i;
RETURN_ENUMERATOR(ary, 0, 0);
rb_ary_modify(ary);
for (i = 0; i < RARRAY_LEN(ary); i++) {
rb_ary_store(ary, i, rb_yield(RARRAY_PTR(ary)[i]));
}
return ary;
}
可以明显看出这段代码与 MRI 版本的相似性。不过,YARV 相比 MRI 有显著进步。主要区别在于,MRI 支持 Ruby 1.8,而 YARV 采用了 Ruby 1.9 版本。在 YARV 中创建
Object
类的代码显示,它以 Ruby 1.9 风格的
BasicObject
作为超类:
rb_cBasicObject = boot_defclass("BasicObject", 0);
rb_cObject = boot_defclass("Object", rb_cBasicObject);
rb_cModule = boot_defclass("Module", rb_cObject);
rb_cClass = boot_defclass("Class", rb_cModule);
另一个更微妙的区别是,在运行 Ruby 代码时,YARV 在解析树和执行之间增加了一个额外步骤。解析 Ruby 源代码后,YARV 将生成的解析树转换为大致扁平的字节码列表,实际执行的就是这些字节码。例如,以下 Ruby 代码片段:
if denominator != 0
quotient = numerator / denominator
end
在 YARV 中会转换为类似下面的字节码列表:
0014 trace 1 ( 4)
0016 getdynamic denominator, 0
0019 putobject 0
0021 opt_neq <ic>, <ic>
0024 branchunless 41
0026 trace 1 ( 5)
0028 getdynamic numerator, 0
0031 getdynamic denominator, 0
0034 opt_div
0035 dup
0036 setdynamic quotient, 0
0039 leave ( 4)
0040 pop
0041 putnil ( 5)
尽管执行字节码和原始解析树的区别看似深奥,但效果显著:YARV 和它的字节码执行速度比 MRI 快得多。
3. JRuby:在 JVM 中融入 Ruby
与 YARV 自带自研虚拟机不同,JRuby 的开发者面临的挑战是将 Ruby 适配到现有的 Java 虚拟机(JVM)上。他们的解决方案是将 Ruby 完全用 Java 实现并集成到 Java 中。由于在 Java 环境中运行与构建 C 应用面临不同挑战,JRuby 的源代码与 MRI 和 YARV 有所不同,但仍有迹可循。
下载并解压 JRuby 源代码归档文件后,会发现它是一个非常传统的 Java 开发项目。在顶级目录下,有一个
ant build.xml
文件以及
bin
、
lib
和
src
目录。
map!/collect!
方法在 JRuby 中的实现如下:
/** rb_ary_collect_bang
*
*/
public RubyArray collectBang(
ThreadContext context, Block block) {
if (!block.isGiven())
throw context.getRuntime().
newLocalJumpErrorNoBlock();
modify();
for (int i = 0, len = realLength; i < len; i++) {
store(i, block.yield(context, values[begin + i]));
}
return this;
}
在 C 或 Java 中,
collect!
方法的逻辑基本相同:遍历数组,依次替换元素的值。
4. Rubinius:用 Ruby 实现 Ruby
如果你对 Ruby 实现感兴趣,但又被 Ruby 书中大量的 C 和 Java 代码弄得晕头转向,那么 Rubinius 值得一看。Rubinius(http://rubini.us)的目标是用 Ruby 实现 Ruby,虽然它尚未完全完成,但却是了解 Ruby 实现知识的重要来源。
map!
方法在 Rubinius 中的实现如下:
# Replaces each element in self with the return value
# of passing that element to the supplied block.
def map!
Ruby.check_frozen
return to_enum(:map!) unless block_given?
i = -1
each { |x| self[i+=1] = yield(x) }
self
end
使用 Ruby 作为实现语言,代码编写会相对容易一些。
5. 拓展 Ruby 实现的方法
深入了解 Ruby 实现的一个好方法是研究如何扩展它。每个 Ruby 实现都允许向原生实现添加功能。由于扩展需要对实现有一定理解,因此“如何扩展 Ruby”的文档是了解 Ruby 工作原理的重要资源。
-
MRI 和 YARV
:其源代码中都包含一个
README.EXT文件,能很好地解释扩展的基础知识。 -
JRuby
:其项目网站有专门的章节解释 JRuby 的工作原理,相关文档的 URL 为
www.kenai.com/projects/jruby/pages/Internals。
6. 避免过度优化的陷阱
了解 Ruby 实现时,唯一需要注意的是避免过度思考底层实现。有些人可能会认为,编写每个 Ruby 类和调用每个方法都会触发大量的 C 或 Java 代码,这会导致代码运行缓慢,从而考虑减少类的编写或编写更长的方法。但实际上,半个世纪的软件工程经验表明,应该先编写代码,只有在代码运行过慢时才考虑优化。正如 Donald Knuth 所说:过早优化是万恶之源。不要因对 Ruby 实现的一点了解而忽视这一基本事实,在深入探究之前,你的 Ruby 代码已经足够快,现在依然如此。
以下是几种 Ruby 实现的对比表格:
| 实现 | 基础语言 | 支持 Ruby 版本 | 特点 |
| ---- | ---- | ---- | ---- |
| MRI | C | 1.8 | 原始实现,代码结构为后续版本奠定基础 |
| YARV | C | 1.9 | 引入字节码执行,速度更快,采用新的类继承结构 |
| JRuby | Java | - | 适配 JVM,与 Java 集成度高 |
| Rubinius | Ruby | - | 目标是用 Ruby 实现 Ruby,代码编写更符合 Ruby 风格 |
mermaid 流程图展示 Ruby 代码在 YARV 中的执行过程:
graph LR
A[Ruby 源代码] --> B[解析树]
B --> C[字节码列表]
C --> D[执行字节码]
总之,通过对 MRI、YARV、JRuby 和 Rubinius 等 Ruby 实现的了解,我们能更深入地认识 Ruby 的工作原理。尽管很多人学习 Ruby 是为了避免编写 C 或 Java 代码,但了解这些实现能让 Ruby 不再神秘,从而成为更优秀的 Ruby 程序员。
深入了解 Ruby 实现:MRI、YARV、JRuby 与 Rubinius
7. 灵活运用 Ruby 的规则与建议
在学习和使用 Ruby 的过程中,我们会接触到很多规则和建议。一方面,我们强调 Ruby 的技术灵活性,如无需静态类型的束缚、能动态修改类以及灵活的语法便于构建 DSL 等;另一方面,又会有一些编程规范,如代码缩进方式、谨慎使用
method_missing
以及避免使用类变量等。
但实际上,这些规则并非绝对。真正掌握一门编程语言,不是单纯地遵循规则,而是在了解规则的基础上,在特定情况下敢于打破规则。正如 George Orwell 所说:“与其说出彻头彻尾的粗鄙之语,不如打破这些规则。” 如果在编程过程中遇到需要打破规则的情况,可以访问
www.eloquentruby.com
或联系
russ@russolsen.com
分享你的经历。
8. 进一步学习 Ruby 的资源推荐
如今关于 Ruby 的优秀书籍众多,以下为不同学习需求的人提供一些经典推荐:
-
基础学习
:
-
《Programming Ruby 1.9: The Pragmatic Programmers’ Guide》
:全面且自由流畅地探索 Ruby 编程语言。
-
《The Ruby Programming Language》
:内容系统,适合喜欢系统学习的人。
-
《Beginning Ruby: From Novice to Professional, Second Edition》
:帮助新手逐步成长为专业开发者。
-
《The Well - Grounded Rubyist》
:深入讲解 Ruby 的各个方面。
-
软件开发相关
:
-
《Design Patterns in Ruby》
:探讨如何在 Ruby 中应用设计模式。
-
《The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming, Second Edition》
:提供 Ruby 编程的解决方案和技巧。
-
《Ruby Cookbook》
:包含大量实用的 Ruby 代码示例。
-
《Ruby Best Practices》
:分享 Ruby 编程的最佳实践。
-
正则表达式学习
:
-
《Mastering Regular Expressions》
:深入掌握正则表达式的经典之作。
-
《Regular Expressions Cookbook》
:提供正则表达式的实用菜谱。
此外,还可以通过研究各种 Ruby 项目的源代码,以及借鉴前人的经验来提升自己的编程能力。例如 Paul Graham 的《On LISP》、Bloch 的《Effective Java, Second Edition》、Beck 的《Smalltalk Best Practice Patterns》等书籍,都蕴含着宝贵的编程思想。
9. Ruby 中的符号与运算符
Ruby 中有许多符号和运算符,它们在不同的场景下有不同的用途:
| 符号/运算符 | 用途 |
| ---- | ---- |
|
"
(双引号) | 用于字符串字面量,支持字符串插值 |
|
'
(单引号) | 用于字符串字面量,不支持字符串插值 |
|
-
(减法运算符) | 可作为二元或一元运算符,支持重载 |
|
.
(句点) | 用于匹配任意单个字符,在模块语法中使用,可与星号
*
结合使用 |
|
/
(除法运算符) | 支持重载 |
|
:
(冒号) | 用于符号语法 |
|
::
(双冒号) | 用于模块语法 |
|
;
(分号) | 用于分隔 Ruby 代码中的语句 |
|
\
(反斜杠) | 用于转义字符串和正则表达式中的特殊字符 |
|
|
(或运算符) | 支持重载 |
|
||=
运算符 | 用于基于表达式的初始化 |
|
+
(加法运算符) | 可作为二元或一元运算符,支持重载,具有非交换性 |
|
=~
运算符 | 用于测试正则表达式是否匹配字符串 |
|
==
(双等号运算符) | 具有对称性、传递性等特性,不同数值类对其有不同处理 |
|
===
(三等号运算符) | 用于
case
语句 |
|
=>
(哈希火箭) | 用于哈希语法 |
|
!
(感叹号) | 可用于方法名结尾,表示危险或有副作用的方法,也可作为一元运算符 |
|
%
(格式化或取模运算符) | 用于字符串格式化或取模运算 |
|
%q
| 用于创建任意引号的字符串 |
|
&
(与运算符) | 支持重载 |
|
*
(星号) | 在方法定义中用于处理额外参数,在正则表达式中用于匹配零个或多个字符 |
|
?
(问号) | 用于正则表达式中的可选匹配 |
|
?:
(三元运算符) | 用于基于表达式的决策 |
|
@@
(双 @ 符号) | 用于类变量语法 |
|
[]
(方括号) | 用于数组索引、字符串分隔和正则表达式字符类 |
|
[]=
| 用于索引相关类的赋值操作 |
|
^
(异或运算符) | 支持重载 |
|
{}
(花括号) | 用于代码块语法 |
|
<<
(左移运算符) | 支持重载,可用于集合类添加元素 |
|
<=>
运算符 | 用于
Float
和
Fixnum
类的比较,与
sort
方法相关 |
mermaid 流程图展示 Ruby 中方法调用与代码块的关系:
graph LR
A[方法调用] --> B{是否提供代码块}
B -- 是 --> C[执行代码块]
B -- 否 --> D[正常执行方法]
C --> D
总之,Ruby 是一门功能强大且灵活的编程语言。通过深入了解其不同的实现方式、掌握相关的规则和建议、学习丰富的资源以及熟悉各种符号和运算符,我们能够更好地运用 Ruby 进行编程,开发出高效、优质的软件。
超级会员免费看
7

被折叠的 条评论
为什么被折叠?



