设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: DeathKing
打印 上一主题 下一主题

[讨论] Ruby/RGSS Tips 每日一更 [技术区的版聊帖?]

  [复制链接]

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
44
发表于 2010-10-18 10:50:40 | 只看该作者
本帖最后由 紫苏 于 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 代码。

  1. instrs = RubyVM::InstructionSequence.compile <<-START
  2.         a = 0
  3.         a = a + 1
  4.         a += 2
  5.         p a
  6. START

  7. 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"]

值得注意的还有一个方法:
  1. 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 的性能提升还是有很大空间的。

点评

难说,毕竟各大 Ruby 虚拟机都在进行激烈的竞争,指不定哪天谁把谁超了 o.o YARV 作为官方版压力很大  发表于 2010-10-19 00:45
我很好奇Rubinius和YARV相比性能如何= =  发表于 2010-10-18 22:15
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
43
发表于 2010-10-17 05:24:28 | 只看该作者
本帖最后由 紫苏 于 2010-10-17 05:27 编辑

继续一些和 Ruby 1.9 字符串编码有关的内容——




新的转义序列:\u
在字符串字面值中指定 \u 可以用来表式一个 Unicode 字符,后面紧跟着一个 16 位(双字节 UTF-16BE)十六进制数字:

  1. #coding: GBK

  2. str = "测试".encode(Encoding::UTF_16BE)
  3. p "\u6d4b\u8bd5".encoding
  4. p "\u6d4b\u8bd5".encode(Encoding::GBK, Encoding::UTF_8)
  5. p "\u6d4b\u8bd5".encode(Encoding::GBK)
复制代码
(注:这里的源文件编码使用 GBK 是因为我测试时的源文件是 GBK 编码,下同。)
输出:

#<Encoding:UTF-8>
"测试"
"测试"

当字符串中包含 \u 序列时,尽管你指定的是 UTF-16 大端序的 Unicode 字符,该字符串还是会被自动编码为 UTF-8,并相应设置字符串的编码成员为 UTF-8(默认是和源文件编码相同),所以这里第一行打印出了一个 UTF-8 的 Encoding 对象。

`\u6d4b' 和 `\u8bd5' 这两个字符分别是 `测' 和 `试' 的 UTF-16BE 内码。后两行输出语句是完全一致的,因为字符串对象内部维护着一个编码对象的域,所以 String#encode 可以根据这个域来进行编码。这里它知道 "\u6d4b\u8bd5" 的编码为 UTF-8,所以是否手动指定源编码都无所谓了。由于我测试时使用的是 Windows 的字符终端(即被称为“命令提示符”的控制台),它使用的编码取决于 locale,而我的 locale 的代码页是 GBK,所以只有当我们把这个字符串编码回 GBK 后,才能正常地在字符终端上显示“测试”这两个字。

注意我们这里用的是 Encoding::UTF_8 等常量,而不是 "UTF-8" 这样的字符串,这重利用了预先生成的 Encoding 对象,避免了字符串对象的创建,给 Ruby 的堆和 GC 减少了压力。




String 进行了 Comparable 的 mixin,糅合了一个 `==' 方法,但 String 类对它进行了覆盖,使其比较字符串内容而不是对象的身份。现在 String 多了编码的成员,但 == 并不会进行不同编码间相同字符串的验证,它只是简单地比较每一个字节。




String#codepoints 也是一个新的方法。它返回字符串中所有代码点的枚举器(关于枚举器,我们改天再讨论,如果你还没有概念,可以想象成一个集合,与数组相似但还并未完全是数组)。代码点是一个字符在代码页中对应的、唯一的整数,这些整数的集合就形成了一个“代码空间”(即字符集),比如 7 位 ASCII 字符集,就有 128 个代码点,分别表示 128 个不同的字符。在一个字符集中,一个代码点和一个字符是有一对一关系的,和字节是两码事。比如 `测' 这个字符在 UTF-8 里占据 3 个字节,但是与之对应的只有一个代码点。

  1. #coding: UTF-8

  2. p "测试".codepoints.to_a
复制代码
输出:

[27979, 35797]

你可能发现这两个数字刚好是我们之前看到的 `\u6d4b'、`\u8bd5' 的十进制表示,但这并不代表 UTF-8 和 UTF-16 没区别。UTF-8 和 UTF-16 等 Unicode 编码家族都可以使用相同的代码点和相同的 Unicode 平面,但是在数据存储的细节上有不同。




String#force_encoding,它可以强行改变字符串对象的编码属性(一个 Encoding 成员变量),但它并不会改变底层的内码,而只是改变其它编码的方法(如 String#encode)处理字符串时的行为。
  1. #coding: GBK

  2. str_utf_8 = "测试"
  3. str_gbk = str_utf_8.clone
  4. str_gbk.force_encoding(Encoding::UTF_8)

  5. p str_gbk.encode(Encoding::GBK)
复制代码
输出:

1.rb:7:in `encode': "\xB2" on UTF-8 (Encoding::InvalidByteSequenceError)
        from 1.rb:7:in `<main>'

String#encode 把 str_gbk 当做 GBK 编码来处理,而 str_gbk 的内码实际上还是 UTF-8,所以抛出了这个异常。用户可以用 String#valid_encoding? 来对此进行判断。

点评

沙发~  发表于 2010-10-17 06:43
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
42
发表于 2010-10-16 06:33:14 | 只看该作者
本帖最后由 紫苏 于 2010-10-16 06:38 编辑

通过上一讲,相信同学们都对 Ruby 1.9 的字符串编码有了一定的了解。在底层,1.9 的编码功能实际上是通过一个叫 M17N 的开源引擎实现的,专门用来处理多语言字符串,它在 C 的层面把字符串包装成了一个拥有一些属性的对象。
用心的同学们肯定会记得,我们曾经提到了 Encoding 对象这个概念。Encoding 对象,自然是表示一个编码对象,我们可以用它来代替用来表示编码名称的字符串。Encoding 这个类没有提供实例化的方法,但是它在自己的命名空间中预定义了大量的 Encoding 对象作为静态成员,各自表示一种 Ruby 1.9 支持的编码方式。这是一种经典的设计模式——多例(multiton)模式,与之类似的有单例(singleton)模式,即类只有一个单一实例的模式。

  1. p Encoding.name_list
复制代码
这句脚本会打印出当前支持的所有编码。类似于 name_list 这样的静态方法(类对象单例方法),还有:
Encoding.aliases,它返回一个以别名为键,基础编码名称为值的 Hash 对象,告诉了你所有编码的所有别名,如 CP936 就是 GBK 的别名;
Encoding.compatible?,它测试两个字符串的编码是否兼容,兼容编码的字符串可以直接串连起来;
Encoding.default_external、Encoding.default_external=,它们分别获取、设置默认的外部编码,用于处理输入、输出流时的字符串编码;
Encoding.default_internal、Encoding、default_internal=,它们分别获取、设置默认的内部编码,用于内部数据读写的转码;
Encoding.find,通过编码名称字符串获取对应的 Encoding 对象;
Encoding.list,罗列出所有加载了的 Encoding 对象(之前提到的 name_list 是编码名称的字符串);
Encoding.locale_charmap,返回当前 locale 的代码页,这是由操作系统的设置而决定的,比如 PRC locale 的代码页就是 GBK;

Encoding 的实例也可以通过预定义的常量来引用,如:Encoding::UTF_8、Encoding::GBK。
Encoding 实例的方法,有 dummy?、name 和 names 这三个。dummy? 是用来测试接收者 Encoding 对象是否是 dummy 的(很明显!),即 M17N 并没有真正地实现、支持这种编码,但 Ruby 仍然把它加入了进来,如:UTF-7;name 显然是返回编码的名称;names 则是返回接收者编码对象的所有有效名称:

  1. p Encoding::UTF_8.names
复制代码
输出:
["UTF-8", "CP65001"]
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
41
发表于 2010-10-15 08:16:19 | 只看该作者
本帖最后由 紫苏 于 2010-10-16 00:59 编辑

Ruby 1.9 的一大改进就是对编码的支持有了质的飞跃。今天我们先来看一下 1.8 中令人头疼的字符串编码、解码相关的问题是如何在 1.9 中轻易解决的。
内置库中,String 类多了几个方法——encode、encode!、encoding,前两个都是把接收者字符串按照另一个指定字符集编码的方法,分别是非破坏性和破坏性版本;第三个是用来获取编码方式的访问器。

  1. #encoding: UTF-8
  2. p 'hello'.encode('UTF-16LE')
复制代码
这里顺便介绍一下第一行的作用,它是 Ruby 1.9 的新功能。什么,这不就是行注释?是的,你没看错,它确实是一行注释,但却被称为“magic comment”,因为它是 Ruby 1.9 推荐的用来指定源文件编码的方式。如果一个 Ruby 源文件第一行包含注释,且注释中包含 `coding: ' 这个字串(coding + 冒号 + 空格),那么在 `coding: ' 之后的内容就会被用来作为当前源文件的编码名称,如这里的 UTF-8。在类 Unix 操作系统上,源文件可以包含 Shebang,而 magic comment 则必须是紧接着 Shebang 的下一行,不能有空行。
所以,我们这里是让源文件按照 UTF-8 格式来编码,而这会使得字符串字面值的编码也成为 UTF-8。之后我们通过 String#encode 方法把一个简单的字符串转换为了小端序 UTF-16 编码,也就是平时我们引用 `Unicode' 时所指的编码,这是我们在 Windows 操作系统上调用 API 处理多字节字符(如:中文字符)相关的操作时需要的编码方式。输出:

"h\x00e\x00l\x00l\x00o\x00"

我们就是这么简单地把字符串编码为了本地 API 可识别的形式,而在 Ruby 1.8 中,还得通过调用本地 API 来进行转码,如 Windows 平台上的 MultiByteToWideChar 函数。
如果我们没有给 String#encode 传递参数,那么它会默认使用 Encoding.default_internal(这个以后再讲)来编码。如果传递了两个字符串参数,那么后一个字符串参数则会被作为源编码方式:
  1. p '你好'.encode('UTF-16BE', 'GBK')
复制代码
这里就是把 '你好' 这个字符串当做 GBK 字符串编码为大端序 UTF-16。
String#encode 还接受第三个参数,这个参数指定了编码时额外的选项,包括源字符串中的非法字符以及目标编码中不存在对应代码点的字符的默认替代字符的选项(比如 `?',我们经常能在某些非 Unicode 程序中看见代码页不匹配时出现的一大串 `????',就是因为这些程序用来解码的字符集中没有对应的源字符串中的代码点,这时程序就选择用 `?' 来代替这个它不知道如何表示的字符),不同平台之间回车、换行符转换的选项(Windows 是 `\r\n', Linux 是 '\n', Mac OS 是 '\r')以及 XML PCDATA 特殊字符转换的选项(如 < 转换为 &lt; 等),具体可以参考官方文档中的详细表格,这里给一个处理中文时的情况:

  1. #encoding: GBK
  2. p '你好'.encode('ISO-8859-1')
复制代码
以上会抛出一个 Encoding::UndefinedConversionError 异常,因为 ISO-8859-1 没有中文字符的代码点,而我们又没有指定替换字符。这样的话:

  1. #encoding: GBK
  2. p '你好'.encode('ISO-8859-1', undef: :replace, replace: '?')
复制代码
就可以了,输出:"??"。要注意这里后面实际上是一个参数,是一个以符号为键的 Hash 对象(见本贴之前的相关内容)。

String#encode! 方法和 String#encode 用法一模一样,只不过是原地(破坏性)算法。String#encoding 可以用来获取接收者字符串当前的编码方式。

String#encode 和 String#encode! 表式编码的参数不一定是编码名称字符串对象,也可以是一个 Encoding 对象。关于 Encoding 对象,我们下次再讲解。

点评

……= =b 这让我写Marshal转换的时候十分愉悦  发表于 2010-10-15 17:59
回复 支持 反对

使用道具 举报

Lv2.观梦者 (管理员)

八云紫的式神

梦石
0
星屑
539
在线时间
1238 小时
注册时间
2008-1-1
帖子
4282

烫烫烫

40
发表于 2010-10-13 16:58:41 | 只看该作者
看紫苏大人讲了好多1.9特性,于是我也来弄一下。。。不过级别完全不够所以只说个自己用过的小细节

在1.8中,取string里的一个字符会得到一个整数,其值是那个字符的ASCII码,除非你是C语言控,否则你不会认为这是一项功能而是一个bug
指的庆幸的是在1.9这个缺陷得到了改变,取出的将是一个只包含那个字符的string对象

a = "ABC"
p a[0]
#=> 65 in 1.8
#=> "A" in 1.9

ruby1.9乃太萌了,紫苏大人给点资料吧- -最好有中文

点评

不知道 Programming Ruby 1.9 有没有被翻译成中文 = =  发表于 2010-10-13 19:56
rm for linux(wine)制作中,期待夏娜SAMA能实现到webrm上
回复 支持 反对

使用道具 举报

Lv3.寻梦者

梦石
0
星屑
1040
在线时间
1564 小时
注册时间
2008-7-30
帖子
4418

贵宾

39
 楼主| 发表于 2010-10-11 12:45:16 | 只看该作者


Kernel、Object、Module、Class间的关系。

简洁、明了、没有胃溃疡。

点评

其实匿名类都是不会被 #superclass 获取到的,不只是 Kernel 的 mixin 而已  发表于 2010-10-11 13:58

See FScript Here:https://github.com/DeathKing/fscript
潜心编写URG3中。
所有对URG3的疑问和勘误或者建议,请移步至发布页面。
欢迎萌妹纸催更
回复 支持 反对

使用道具 举报

Lv3.寻梦者

宛若

梦石
0
星屑
1553
在线时间
526 小时
注册时间
2007-8-19
帖子
1493

极短24参与开拓者

38
发表于 2010-10-10 11:12:05 | 只看该作者
为什么此帖被顶上来了- -
然后不留些东西貌似过意不去……
RM的Interpreter是个很诡异的存在,尤其是数据结构保存形式- -
显示文章等带换行的数组保存形式是
事件开始代码
事件开始代码+400(貌似脚本是300)
同时……在eval脚本的时候有一处诡异的地方:
  1.     # 评价
  2.     result = eval(script)
  3.     # 返回值为 false 的情况下
  4.     if result == false
  5.       # 结束
  6.       return false
  7.     end
复制代码
这样导致了如果事件页脚本返回的是false的话就会导致index不能推进,事件卡住的问题- -
于是当脚本是赋值变量为false时只能占用那少得可怜的脚本框写下return true之类的结束语……而且这种错误通常无法发现- -
最后……感谢II提醒脚本卡住的原因- -

点评

本来想创造一楼清一色紫苏的,你个坏人  发表于 2010-10-11 05:29
看了半天才发现是倒序的- -||| 我自行爆破吧 - -|||  发表于 2010-10-10 16:35
= =一天一枚诶……  发表于 2010-10-10 15:15
[url=http://rpg.blue/thread-219730-1-1.html]http://unhero.sinaapp.com/wi.php[/url]
[color=Red]如你所见这是个死坑,没错这就是打我的脸用的[/color]
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
37
发表于 2010-10-10 10:09:59 | 只看该作者
本帖最后由 紫苏 于 2010-10-13 19:54 编辑

Ruby 1.9 引入了纤程(Fiber)的概念。
提起纤程,我们自然而然地就想到了线程(Thread),而事实上当你对比了“纤”和“线”这两个字后,你大概就能猜到,纤程实际上是一种轻量级的线程了。轻量级在这里是指纤程的体积比线程小,更容易维护、调试,但也不具备一些线程才能提供的复杂功能。
在并行计算领域,我们使用线程来构建一个抢先式多任务模型;而纤程则是用于合作式多任务模型。
抢先式多任务模型,顾名思义,是指执行任务的对象的工作模式是抢先式的,工作调度完全由一个具有权利的、客观的存在进行。比如:三个兄弟抢着用电脑,谁也不让谁,但是当妈妈在家的时候,就能够合理地分配、调度、切换各个兄弟使用电脑的时间。又如:我们使用的操作系统,其进程和线程的调度就是一种抢先式任务模型。
合作式多任务模型,显然就是指执行任务的对象之间有团队精神,可以协同工作。第一个对象完成了一部分工作,就可以把成果交给第二个对象,让他在成果的基础上继续工作,并等待他完成。所以,我们使用纤程的主要目的,就是实现这种所谓的“协程”。比如:动物园的售票系统模型,观光者需要售票员处理售票相关的事项,然后把票递给自己;而售票员需要观光者把钱递给自己。这是在纤程的概念还没有出现时就存在于线程技术中的生产者与消费者关系的概念。
来看一个简单的例子:

  1. fibonacci = Fiber.new {
  2.         a = b = 1
  3.         loop {
  4.                 b += a
  5.                 a = b - a
  6.                 Fiber.yield b
  7.         }
  8. }

  9. 10.times {
  10.         p fibonacci.resume + 1000
  11. }
复制代码
输出:
1002
1003
1005
1008
1013
1021
1034
1055
1089
1144

我们这里是实现了打印出一个 Fibonacci 序列中前 10 个数 各加上 1000 后的结果的协程。fibonacci 是我们的生产者纤程,它的任务是生产出 Fibonacci 序列。在循环内部,Fiber.yield 会暂停执行,把执行权让给调用纤程的代码,并把参数作为 Fiber#resume 的返回值传递回去。在纤程外部,我们有一个十步的循环,作用是把 Fibonacci 序列前 10 个数各加上 1000 并打印出来。每次调用 Fiber#resume,都会恢复纤程的执行,并返回 Fiber.yield 的参数。这时如果我们给 resume 传递了参数,该参数就会被作为 Fiber.yield 的返回值传递给纤程(当然我们这里没有用到这个功能,但在模拟更复杂的合作式多任务时,是极有可能需要双向交互的)。
同样的效果,我们可以用一个循环就完成,即在生产 Fibonacci 数的同时打印(循环内部生成一个就打印一个),但这被认为是差劲的设计模式,因为它把生产和消费这两个过程混合到了一起。使用纤程的话,我们就能使它们泾渭分明。
同样的效果,我们也可以用线程来实现。但线程相比于纤程是重量级的,我们需要考虑对象锁之类的条件变量来进行线程同步,同时还得考虑潜在的线程安全问题。而我们的纤程,则拥有同步的天性,使得我们在使用时无须顾虑各种线程相关的复杂问题。
Ruby 1.9 内置的纤程只适用于小规模协程(二人合作),因为 Fiber.yield 只能把执行权返回给 Fiber#resume 的调用者。如果想实现规模更大的协程,我们就需要使用 Ruby 1.9 标准库中的 Fiber 了(不属于这帖讨论范畴)。

点评

萌物  发表于 2010-10-13 17:20
moy
每天都有新知识,欧也~  发表于 2010-10-11 15:55
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
36
发表于 2010-10-9 08:35:46 | 只看该作者
Ruby 1.9 有了新的 Hash 结构和语法。在 1.9 中,你可以用以下方式更快速地写出一个包含以符号为键的元素的 Hash 对象:
  1. #coding: GBK
  2. p ({ 赵天吉: "大刀会", 朱红灯: "义和团" })
复制代码
输出:
{:赵天吉=>"大刀会", :朱红灯=>"义和团"}
注意这种新的 Hash 字面值只针对于符号键。
结构方面,也有了一个大革命。Hash,顾名思义,一堆杂乱的符号,自然没什么顺序可言,随着我们对 Hash 的了解越深,“Hash 不保证顺序”的概念就越来越深入我们脑海。然而这次,顽皮的 Ruby 又一次颠覆了用户的观念:Hash 也可以是有序的!
在 1.8 中,Hash 对象即是使用分离链接法解决哈希碰撞的散列表。表中每一项需要四个东西:散列码、键的引用、值的引用、下一个散列码相同的元素的引用。为了实现有序的散列表,Ruby 1.9 在表的元素结构体中增加了两个新的指针:指向按顺序排列的前一个和后一个元素的指针,这使得在对散列表进行插入和删除操作时能够维护一个有序的链,实际上就是一个和旧的散列表结构穿插在一起的双向循环链表结构,这两种结构的并集就是新的 Hash 结构了。这个改动的代价是:1.9 的 Hash 的 插入和删除变得更慢了些。这个改动的好处是:Hash 的遍历、迭代、枚举也有序了。同时,因为内部维护着双向循环链表,1.9 遍历 Hash 的速度也比 1.8 快,因为 1.8 必须要遍历整个散列表的桶,自然也就必须经过一些桶中的空位。
对于为了有序遍历而损失插入、删除的效率是否值得的问题,仁者见仁,智者见智。有些人用散列表来存储一种字典式的结构,然后需要把内容按照有序的方式打印出来,这是常有的;而有些人仅仅是用散列表来进行快速的保存和读取数据。不过我个人倒是有点不赞成直接改动原来的 Hash 类,我们完全可以设计一个新的类,比如 OrderedHash,这样就能让用户有一个选择。

点评

散列表和数组在不同的方面效率不同,还是给用户保留选择为好  发表于 2010-10-13 19:50
我极为赞成像php那样把hash并入Array  发表于 2010-10-13 17:21
嘛,高中的时候自学了点html和js的皮毛…然后后来重拾了下,看了点js的面向对象,然后就被Ruby带走了  发表于 2010-10-11 13:43
有序哈希我一般的针对方法是同步的双数组= =b  发表于 2010-10-10 15:16
67以前是搞JS的?o.o  发表于 2010-10-10 00:01
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
35
发表于 2010-10-8 07:01:16 | 只看该作者
本帖最后由 紫苏 于 2010-10-8 07:13 编辑

Ruby 1.9 引入了新的语法支持块局部变量,同时也改变了原来的块参数行为。在块原本的参数列表中,使用一个分号 `;' 可以分隔块的参数和局部变量声明,如下:
  1. l1 = l2 = 1
  2. lambda{ |arg1, arg2 ; l1, l2|
  3.         l1 = 1234567
  4.         l2 = 7654321
  5.         p "参数是#{arg1},#{arg2}"
  6. }.call(126, 127)
  7. p l1                                        # => 1
  8. p l2                                        # => 1
复制代码
其中 arg1、arg2 是块的参数(call 的时候需要传递),而 l1、l2 则是块局部变量的声明。块局部变量的作用域仅限于块的上下文,且声明的块局部变量必须出现在块参数后面。
这种机制也是为了进一步完善闭包,毕竟早在 Lisp 时代就有 let 语法支持闭包局部变量。这实际上就是变量遮蔽机制在闭包这个场合的一个特例——作用域小的变量遮蔽了作用域大的变量,使得作用域大的变量不可见。
块的参数在 Ruby 1.8 中并不一定是局部的,如果外部有同名变量,那么它引用的仍然是外部的同名变量。在 Ruby 1.8 中,以下代码中的块变参数赋值会改变块外部变量的值(也就是没有了遮蔽):
  1. l1 = l2 = 1
  2. lambda{ |l1, l2|
  3.         p "参数是#{l1},#{l2}"
  4.         l1 = 1234567
  5.         l2 = 7654321
  6. }.call(126, 127)
  7. p l1                                        # => 1234567
  8. p l2                                        # => 7654321
复制代码
而在 Ruby 1.9 中,块的参数永远是局部的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-4-20 08:40

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表