赞 | 2 |
VIP | 143 |
好人卡 | 1 |
积分 | 1 |
经验 | 216792 |
最后登录 | 2019-10-10 |
在线时间 | 24 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 61
- 在线时间
- 24 小时
- 注册时间
- 2008-8-5
- 帖子
- 1924
|
本帖最后由 紫苏 于 2010-10-19 00:48 编辑
连续弄了三天编码,同学们想必都有点腻了,老实说我自己也有点反胃,还剩下 I/O 部分的一些编码相关内容,就留待改日补完了吧!
(以下提及的 Ruby,均指官方的 Ruby 实现)
今天我们来简单的看一下提升 Ruby 1.9 性能的大功臣——YARV。
YARV,全称是 Yet Another Ruby VM,VM 的全称是 Virtual Machine。这个名称,翻译为中文,就是“又一个 Ruby 虚拟机”。这种以 `Yet Another' 形式开头的命名是一种黑客行话,在有英语语感的程序员看来,这种命名有三分幽默诙谐之感,在业内十分受欢迎。
Ruby 的虚拟机,就和 Oracle 的 JVM(Java 虚拟机)、微软的 CLR(.NET) 一样,是一种由软件实现的虚拟的机器构架,有着自己的指令集,并可以像物理机器一样执行指令。这种针对某个(家族)语言设计的虚拟机被称为进程虚拟机,因为它只是给操作系统上某一个进程(解释器)服务的;与之并列的是系统虚拟机,它需要分享整个系统的物理硬件资源,比如我们 VirtualBox 下的 Guest OS,就是一种虚拟一整个操作系统的技术。
为什么 Ruby 需要虚拟机?大多数程序员都是人类,人类写的代码,通常是按照人类思考的方式去对机器下达命令,但并不一定有着最高的执行效率。同样效果的代码,在很高程度上都存在着另一份更高效的途径,而我们把找寻并迈向这个途径的过程称为优化。对于编译性语言,我们可以设计一个具有自动优化(同时从源代码层、汇编层)的编译器来一次性最优化我们的代码,并编译为机器码;而对于解释性语言,由于我们是在动态地解释脚本,所以不能把代码一次性地优化并编译为机器码。
Ruby 1.8 的解释器被称为 Matz's Ruby Interpreter(MRI),它解析 Ruby 源文件,并生成一个语法树,然后直接在这个语法树上进行动态求值。这样的过程是毫无优化的。目前主流的解决方案大致可分为两种,其一是采用预编译(Ahead-of-time Compilation),其二是采用即时编译(Just-in-time Compilation)。就 Ruby 1.9.2 的 YARV 来说,它只有前者。所谓预编译,顾名思义,就是预先把代码编译。什么,我之前不是说过“不能把代码一次性地优化并编译为机器码”?是的,的确是这样,但这里的“预编译”不是编译为机器码,而是编译为一种中间代码,通常被称为字节码。字节码和机器码的不同在于,前者是平台无关的,且具备动态性,是由解释器(虚拟机)动态解析的,而不像机器码是直接交给底层的硬件(CPU)去执行。在把可供人类阅读的源代码编译为字节码的过程中,我们就可以进行各式各样的优化,最后产生的字节码,在大部分场合下都比原来的源代码更加高效。
(题外话: YARV 的作者 Sasada Koichi 也曾尝试过引入 JIT 即时编译,进一步提高 YARV 的性能,但 YARV 的主页很久没有更新了,也不知进展如何。JIT 本身是一个十分复杂、很难实现的技术,可能对他来说并不是一个人能实现的了的。也许,Ruby 2.0 会……)
YARV 就是这样的一个虚拟机,它忠心耿耿地优化着我们所写下的 Ruby 代码,几十年如一日,在大量的 Benchmark 测试下,展示了 Ruby 1.9 几近于 Ruby 1.8 两倍的性能。
在 Ruby 1.9 的官方实现(CRuby)中,有了一个内置的类,叫做 RubyVM。它提供给了用户一些在运行时获取 YARV 状态的实用接口,这里介绍几个比较重要的类和方法。
RubyVM::InstructionSequence,这是一个 YARV 指令序列的类,它可以用来动态地编译、反汇编、执行 Ruby 代码。
- instrs = RubyVM::InstructionSequence.compile <<-START
- a = 0
- a = a + 1
- a += 2
- p a
- START
- puts instrs.disassemble
复制代码 这里我们先编译了四行简单的 Ruby 代码,并建立了一个 RubyVM::InstructionSequence 对象。之后我们把反汇编后的指令序列按照 Ruby 提供的可供人类阅读的方式打印了出来,输出:
== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 putobject 0
0004 setlocal a
0006 trace 1 ( 2)
0008 getlocal a
0010 putobject 1
0012 opt_plus
0013 setlocal a
0015 trace 1 ( 3)
0017 getlocal a
0019 putobject 2
0021 opt_plus
0022 setlocal a
0024 trace 1 ( 4)
0026 putnil
0027 getlocal a
0029 send :p, 1, nil, 8, <ic>
0035 leave
可以看到,四行 Ruby 代码在 YARV 层实际上就是这样的指令序列。我们可以通过 RubyVM::INSTRUCTION_NAMES 来打印出 YARV 所有的指令:
["nop", "getlocal", "setlocal", "getspecial", "setspecial", "getdynamic", "setdy
namic", "getinstancevariable", "setinstancevariable", "getclassvariable", "setcl
assvariable", "getconstant", "setconstant", "getglobal", "setglobal", "putnil",
"putself", "putobject", "putspecialobject", "putiseq", "putstring", "concatstrin
gs", "tostring", "toregexp", "newarray", "duparray", "expandarray", "concatarray
", "splatarray", "checkincludearray", "newhash", "newrange", "pop", "dup", "dupn
", "swap", "reput", "topn", "setn", "adjuststack", "defined", "trace", "definecl
ass", "send", "invokesuper", "invokeblock", "leave", "finish", "throw", "jump",
"branchif", "branchunless", "getinlinecache", "onceinlinecache", "setinlinecache
", "opt_case_dispatch", "opt_checkenv", "opt_plus", "opt_minus", "opt_mult", "op
t_div", "opt_mod", "opt_eq", "opt_neq", "opt_lt", "opt_le", "opt_gt", "opt_ge",
"opt_ltlt", "opt_aref", "opt_aset", "opt_length", "opt_succ", "opt_not", "opt_re
gexpmatch1", "opt_regexpmatch2", "opt_call_c_function", "bitblt", "answer"]
值得注意的还有一个方法:- p RubyVM::InstructionSequence.compile_option
复制代码 输出:
{:inline_const_cache=>true, :peephole_optimization=>true, :tailcall_optimization
=>false, :specialized_instruction=>true, :operands_unification=>false, :instruct
ions_unification=>false, :stack_caching=>false, :debug_level=>0}
这其实是 YARV 使用的一些优化方法。就目前来说,有很多高级优化技术,YARV 都还没有使用上,可见 Ruby 的性能提升还是有很大空间的。 |
|