30、深入了解 Ruby 实现:MRI、YARV、JRuby 与 Rubinius

深入了解 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 进行编程,开发出高效、优质的软件。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值