“Ruby Under a Microscope”的版本间的差异
来自Dennis的知识库
Dennis zhuang(讨论 | 贡献) |
Dennis zhuang(讨论 | 贡献) |
||
第23行: | 第23行: | ||
* Ripper.sexp 输出 parse 结果,也可以使用命令行 ruby --dump parsetree xxxx.rb 得到。前者是 Ripper 的 AST 展示格式,后者是实际内部的 c 语言 node 节点信息。 | * Ripper.sexp 输出 parse 结果,也可以使用命令行 ruby --dump parsetree xxxx.rb 得到。前者是 Ripper 的 AST 展示格式,后者是实际内部的 c 语言 node 节点信息。 | ||
* Ruby 使用手写的 tokenizer ,以及 bison 写的 parser —— [https://github.com/ruby/ruby/blob/510f0ec86912e31babaadf1f66bf2a82351c1359/parse.y parse.y] ,bison生成的解释器是 [https://en.wikipedia.org/wiki/LALR_parser LALR Parser]。 | * Ruby 使用手写的 tokenizer ,以及 bison 写的 parser —— [https://github.com/ruby/ruby/blob/510f0ec86912e31babaadf1f66bf2a82351c1359/parse.y parse.y] ,bison生成的解释器是 [https://en.wikipedia.org/wiki/LALR_parser LALR Parser]。 | ||
+ | |||
+ | == 编译 == | ||
+ | |||
+ | * Ruby 1.8 没有编译器, Ruby 1.9 之后引入了 YARV( yet another ruby vm) 中间指令。但是 Ruby 并没有独立的编译器,而是在运行时动态编译成字节码,并交给 VM 解释执行。很可惜, Ruby 还是没有 JIT,将字节码编译成本地机器码。但是从测试来看, 1.9 之后的性能,已经远比 1.8 高(简单测试是 4.25 倍左右), 1.8 还是原始的解释执行 AST 的方式。 | ||
+ | * 编译的过程本质是遍历 AST ,然后生成 YARV 字节码的过程,具体参考 https://github.com/ruby/ruby/blob/trunk/compile.c 中的 iseq_compile_each 函数,一个大的 switch 派发。 | ||
+ | * NODE_SCOPE 表示开始一个新的作用域,作用域绑定着一个本地表 local table,类似 JVM 里的局部变量区,参数和局部变量的信息会放在这里。 | ||
+ | * 查看 YARV 字节码: | ||
+ | |||
+ | <pre> | ||
+ | <code> | ||
+ | code = <<END | ||
+ | 10.times do |n| | ||
+ | puts n | ||
+ | end | ||
+ | END | ||
+ | |||
+ | puts RubyVM::InstructionSequence.compile(code).disasm | ||
+ | </code> | ||
+ | </pre> | ||
+ | |||
+ | 输出 | ||
+ | <pre> | ||
+ | == disasm: #<ISeq:<compiled>@<compiled>>================================ | ||
+ | == catch table | ||
+ | | catch type: break st: 0002 ed: 0008 sp: 0000 cont: 0008 | ||
+ | |------------------------------------------------------------------------ | ||
+ | 0000 trace 1 ( 1) | ||
+ | 0002 putobject 10 | ||
+ | 0004 send <callinfo!mid:times, argc:0>, <callcache>, block in <compiled> | ||
+ | 0008 leave | ||
+ | == disasm: #<ISeq:block in <compiled>@<compiled>>======================= | ||
+ | == catch table | ||
+ | | catch type: redo st: 0002 ed: 0010 sp: 0000 cont: 0002 | ||
+ | | catch type: next st: 0002 ed: 0010 sp: 0000 cont: 0010 | ||
+ | |------------------------------------------------------------------------ | ||
+ | local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) | ||
+ | [ 2] n<Arg> | ||
+ | 0000 trace 256 ( 1) | ||
+ | 0002 trace 1 ( 2) | ||
+ | 0004 putself | ||
+ | 0005 getlocal_OP__WC__0 2 | ||
+ | 0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache> | ||
+ | 0010 trace 512 ( 3) | ||
+ | 0012 leave ( 2) | ||
+ | </pre> | ||
+ | |||
+ | 其中的 local table 就是本地表,<code><callinfo!mid:times, argc:0>, <callcache>, block in <compiled></code> 这里表示为 10.times 传递了一个 Block,它的指令在下面。 |
2016年12月10日 (六) 15:12的版本
分词与语法解析
- 使用 Ripper 输出 lex 结果。
<code> require 'ripper' require 'pp' #ripper is not parser, it can't find error. code = <<STR 10.times do |n| puts n end STR puts code pp Ripper.lex(code) </code>
- Ripper.sexp 输出 parse 结果,也可以使用命令行 ruby --dump parsetree xxxx.rb 得到。前者是 Ripper 的 AST 展示格式,后者是实际内部的 c 语言 node 节点信息。
- Ruby 使用手写的 tokenizer ,以及 bison 写的 parser —— parse.y ,bison生成的解释器是 LALR Parser。
编译
- Ruby 1.8 没有编译器, Ruby 1.9 之后引入了 YARV( yet another ruby vm) 中间指令。但是 Ruby 并没有独立的编译器,而是在运行时动态编译成字节码,并交给 VM 解释执行。很可惜, Ruby 还是没有 JIT,将字节码编译成本地机器码。但是从测试来看, 1.9 之后的性能,已经远比 1.8 高(简单测试是 4.25 倍左右), 1.8 还是原始的解释执行 AST 的方式。
- 编译的过程本质是遍历 AST ,然后生成 YARV 字节码的过程,具体参考 https://github.com/ruby/ruby/blob/trunk/compile.c 中的 iseq_compile_each 函数,一个大的 switch 派发。
- NODE_SCOPE 表示开始一个新的作用域,作用域绑定着一个本地表 local table,类似 JVM 里的局部变量区,参数和局部变量的信息会放在这里。
- 查看 YARV 字节码:
<code> code = <<END 10.times do |n| puts n end END puts RubyVM::InstructionSequence.compile(code).disasm </code>
输出
== disasm: #<ISeq:<compiled>@<compiled>>================================ == catch table | catch type: break st: 0002 ed: 0008 sp: 0000 cont: 0008 |------------------------------------------------------------------------ 0000 trace 1 ( 1) 0002 putobject 10 0004 send <callinfo!mid:times, argc:0>, <callcache>, block in <compiled> 0008 leave == disasm: #<ISeq:block in <compiled>@<compiled>>======================= == catch table | catch type: redo st: 0002 ed: 0010 sp: 0000 cont: 0002 | catch type: next st: 0002 ed: 0010 sp: 0000 cont: 0010 |------------------------------------------------------------------------ local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) [ 2] n<Arg> 0000 trace 256 ( 1) 0002 trace 1 ( 2) 0004 putself 0005 getlocal_OP__WC__0 2 0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache> 0010 trace 512 ( 3) 0012 leave ( 2)
其中的 local table 就是本地表,<callinfo!mid:times, argc:0>, <callcache>, block in <compiled>
这里表示为 10.times 传递了一个 Block,它的指令在下面。