-
2007-09-24
XRuby 0.3.1和Ruby Hacking Guide中文版
今天是一个发布的日子,XRuby发布了0.3.1,Ruby Hacking Guide中文版发布了第一部分。
XRuby 0.3.1
相比于前一个版本,XRuby 0.3.1最大的进步在于完成标准库的预编译。预编译意味着什么?标准库代码无需在每次运行时编译,这意味着今后使用XRuby的标准库性能会得到一定的提升。
有一个与编译相关的话题。之前,Jon Tirsen曾经谈到JRuby的一个问题,运行在AppServer中会有占用太多内存。经过分析得知,为了提高程序的并发性,程序运行会启动多个JRuby。每个JRuby解析Ruby脚本都会建立一棵完整的语法树,这就意味着,由于这种解析模式本身的限制,对于同样的内容,内存中需要保存多份相同的语法树,这种做法意味着无谓的耗用了大量的内存。采用编译的做法,则可以很好的避免这个问题。因为在运行时,相同的是字节码,而JVM很好的帮我们解决字节码共享问题,无需耗用大量的内存。RHG终于完成了第一次发布。已经发布的第一部分介绍的是Ruby的对象模型。我正是从这个部分开始了解Ruby实现的,进而完成了XRuby的Runtime的重写。所以,我一直觉得这部分是了解Ruby实现非常好的一个起点。
从翻译Ruby Hacking Guide到现在已经超过了一年,从第一次发布消息算起也超过了9个月。相比XRuby,这个项目的进展可以用异常缓慢形容。这是一本日文书,也是一本技术书,而且是一本讲语言实现的书。任何一个点都会增加翻译的难度。几个懂日语的朋友先进行一遍初译,然后,我对再对译稿进行一遍校验,并根据自己的理解修改译稿,这样的过程无疑延长了处理的时间。这是一个业余时间的项目,而我更多的业余时间在XRuby上,没有太多精力投入上面。种种的因素造成了这个项目的一托再托。
目前,我手头已经有了第二部分全部和第三部分几章的初译稿,不过,按照之前的进度来看,这几章的发布可能要等到许久之后了。如果你有兴趣,可以加入到这个项目中来,这样,有助于加快这个项目的进度。 -
2007-09-19
管窥Ruby——类的变量(更新版)
《管窥Ruby——类的变量》写在去年,写成之后便更新了一次,因为最初的描述存在一些偏差。即便如此,jxb8901依然指出了其中的一些不足。最近,dennis-zane再次提出了这个问题。回过头来仔细品味,确实有些地方写得不是很到位,索性把它重新写过。
管窥Ruby——类的变量
变量和方法是面向对象难以割舍的两个重要组成部分。在《管窥Ruby——类的方法》中,我们谈到方法,沿着这条路继续,我们再来看看类中的变量。
开始之前,我们还是要再次回顾RClass的定义:
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
(ruby.h)
如果你看过《管窥Ruby——类的方法》,了解了方法存储方式,变量的存储方式便也一目了然了,同样的st_table,意味着同样的处理方式。
不得不承认的一点是,在讨论类的方法时,我故意忽略了一个事实:方法分为类的方法和实例的方法两种。如果对其它语言实现有些许了解,我们知道,这两种方法差别仅仅是this(C++或Java的说法),到了底层时,这个差别可以视为无物,可以统一存放。关于这点,有兴趣可以参考一下《深入Java虚拟机》,它的第五章讲解了虚拟机内方法的表现形式。在Ruby中,类的方法和实例的方法并不是存放在一起的,这里定义的实际上是实例方法,而类的方法是定义在类的Singleton类中。
遇到变量时,我们会碰到同样的问题:类的变量和实例变量是无法统一管理的。因为类的变量只有唯一的一份,而实例变量则是每个实例都有一份。所以,RClass存放的并不是真正实例变量。代码是说明问题的最好方式,下面这段代码说明了如何设置实例变量:
VALUE
rb_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable");
if (OBJ_FROZEN(obj)) rb_error_frozen("object");
switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
default:
generic_ivar_set(obj, id, val);
break;
}
return val;
},
(variable.c)
抛开前面那些复杂的东西,直接来看switch语句后面的内容。这里存在两种情况,对于存在T_OBJECT、T_CLASS和T_MODULE标记的,变量会写入iv_tbl,而其余的情况则转交 generic_ivar_set处理。iv_tbl是“实例变量表(instance value table)”的缩写,不过,如果当前对象是个类对象,这个“实例”变量实际上就是类变量,所以,这个名字多少有些名不符实。
除此之外,还有一个generic_ivar_set。下面是generic_ivar_set的实现:
static void
generic_ivar_set(obj, id, val)
VALUE obj;
ID id;
VALUE val;
{
st_table *tbl;
if (rb_special_const_p(obj)) {
special_generic_ivar = 1;
}
if (!generic_iv_tbl) {
generic_iv_tbl = st_init_numtable();
}
if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
FL_SET(obj, FL_EXIVAR);
tbl = st_init_numtable();
st_add_direct(generic_iv_tbl, obj, (st_data_t)tbl);
st_add_direct(tbl, id, val);
return;
}
st_insert(tbl, id, val);
}
(variable.c)
同样忽略一些非主干的部分,我们看到,这段代码先在一个generic_iv_tbl中进行查找,用作查找键值的是对象实例(obj),而目标同样是一个st_table。得到这个表之后,利用变量名做键值将值插入到表中。我们便不难分析在这种做法中实例变量的存储方式。存在一个全局的实例变量表,走到这里的实例都在其中拥有一席之地,而实例变量存储在实例对应的表中。Ruby通过这样一个二级结构,解决了这些实例变量存储的问题。
了解过基本的做法之后,随之而来的一个问题就是,这些代码都会在什么情况下起作用。
代码已经说得很明白了,只有在有那几个标记的情况下,才会直接调用iv_tbl。T_CLASS和T_MODULE都很好理解,那什么情况下会有T_OBJECT呢?在C Ruby中,在Ruby层次上定义的类,生成的实例都是会标有T_OBJECT(参见《管窥Ruby——Allocator》)。所以,所有由Ruby层次上生成的对象都会走到这里来。
那generic_ivar_set呢?除了那几个标志外的其他部分都会走到这里。除此之外的标志表示什么呢?读一下代码我们便不难发现,几乎就是builtin的几个类,比如数组、字符串、正则表达式等等。那为什么这些builtin类没有一个iv_tbl。对于这些builtin而言,它们真正的实例变量都是以C的形式给出,所以,额外存在一个iv_tbl实际上是一种空间上的浪费。虽然不是常态,但我们依然可以为这些builtin类添加自己的实例成员。为了保持Ruby的动态特性,这才有了generic_ivar_set的存在。
-
2007-09-17
闲聊C++单元测试框架
今天下午,和Darwin聊了一下C++单元测试框架,主要参考对象是CppUnit和CxxTest。
表现形式
因为C++不支持reflection,所以,必须要做一些额外的工作,让框架知道相关内容的存在。CppUnit的做法是用宏进行注册。这种做法要求我们每添加一个测试,就要考虑用相应的宏进行注册,这种做法很繁琐,最大的问题在于由于疏忽而遗漏,这种靠人工保证的东西不可靠。在这点上,CxxTest做得要好一些,有一个专门的脚本做这件事。通过这个脚本扫描这个自己编写的文件,生成一些新的文件,完成这个工作。从代码的表现力和可靠度来说,要好得多。唯一的问题是引入了一个脚本,而且这个脚本一般是由某些动态语言写成的(目前的CxxTest有Perl和Python的脚本),从而引入了对这种语言的依赖。不过,由于C++语言本身的限制,从接口的角度来看,这种做法已经很不错了。
语法
有一种C++的单元测试框架叫TUT,Template Unit Test的缩写。顾名思义,它是用模板完成的(其实,CppUnit和CxxTest都有模板的部分)。随着C++编译器的进步,在大多数情况下,模板都是可以顺利通过编译的。但是,不要忘了,还有一种环境叫嵌入式,那里的编译器基本上还是很原始的,模板并不见得能够顺利的通过编译。
此外,模板还会带来另外一个问题,编译时间的增长,相信有过模板编程经验的人都会对此深有体会。编译时间增长意味着什么?我们接下来讨论。
编译时间
有一种敏捷实践叫做测试驱动开发(Test Driven Development)。测试驱动开发的基础是单元测试。测试驱动开发希望达成的一个目标是快速反馈,所以,站在C++语言的角度,如果执行时间受限于代码本身无法缩短,那么我们希望编译时间尽可能短,这样,才不会把生命都浪费在等待代码编译上。
除了刚才提到的模板问题之外,CppUnit会把所有测试编译生成一个可执行文件,这意味着什么?几乎修改任何一个文件都会造成这个文件的重新生成。随着目标文件的增加,这个过程时间就会增长。相对于修改范围(可能只是某一个文件),还是显得有些长了。为什么Java语言不会存在这种现象?因为Java是动态连接的,所以,Java生成.class就结束了。对应到C++上,这只是完成了目标文件的生成,而在C++我们不得不再进一步生成可执行文件。从道理上,CxxTest可以为不同的测试文件生成不同的可执行文件,不过这么做又少了总体的过程,统计起来又显得心有余力不足了,而且通常不会这么做。
个人而言,对这几个单元测试框架都不是非常了解,如果前面的讨论存在谬误,欢迎有识之士指出。 -
2007-09-10
XRuby一岁了!
一年前,yawl将自己用业余时间做了一年的项目开源了,这就是XRuby。
XRuby project is now hosted on Google Code
有人愿意做Ruby Compiler么?
我就是那时加入XRuby的,依然记得最初见到这个项目时的兴奋,转眼,一年过去了。从2007年1月29日0.1.0发布至今,我们一共发布了7个版本。XRuby正逐渐变得越来越有样子:代码越来越干净,功能越来越强大。
XRuby是我第一次真正全身心投入参与的一个开源项目:常常为自己漂亮的解决了一个问题而自豪,也时常为解决方案不够优雅而寝食难安。依然记得有几次,为了实现一个功能而熬夜;也有本来已经躺在床上,却难以抑制兴奋爬起来继续编码。这一年里,XRuby在成长,我也随着这个项目在成长,对Ruby语言的实现理解越来越深,从最开始的照搬C实现,到现在逐渐有了一些自己的想法在里面。在与大家合作的过程中,从其它人身上学到了许多足以让我受用终身的东西,尤其是yawl。相信其他深入参与XRuby的人与我有着类似的经历和感受吧!
其实,在这一年里,我也并非始终如一的对XRuby付出着。从项目最初开源到发布0.1.0之间有大约4个多月时间,完成了那个新runtime之后,很长一段时间,我并没有写太多代码。那段时间,应该是我参与XRuby过程,感觉最为黑暗的一段时间,因为确实看不到这个项目的方向,没有版本发布,漫无边际的代码等待着编写,而我写的新runtime又很难集成到XRuby里面。这个状态一直持续到0.1.0的发布,我似乎一下子看到了光明,尽管XRuby看起来那么不成熟,但我们的努力终于得到了一丝回报,于是,我兴奋的写下了《XRuby发布了!》 。
在我找回动力之后,XRuby也逐渐开始得到了越来越多的关注,项目成员也逐渐增多,XRuby也逐渐步入开发的正轨。每隔一个多月,我们就会发布新版本,每次新版本的发布,都增强着我们对XRuby的信心。XRuby的成员也通过各种途径向大家介绍着XRuby,也有人开始讨论XRuby。
做开源,最艰难的是什么?技术吗?似乎是,尤其像XRuby,仅仅一个“编译器”的名头,就足以让许多人望而却步了。其实不然,技术这东西,只有不愿意学的,少有难以学会的。参与XRuby并不需要一开始就掌握复杂的编译器技术,因为XRuby包括了许多部分,编译器只是其中的一个部分。时至今日,XRuby中的某些部分对我来说,依然是陌生的,但这并不影响我为XRuby编写代码。从个人的经历来看,builtin是一个很好的入手点,而那里并不多数情况下并不需要了解编译器,甚至几乎不需要了解Ruby内部实现。
在我看来,最难的是坚持。用业余时间,无偿为一个项目付出着。回报?除了知识和技能上的提升,其他都是不可预期的。在这种情况下,坚持着实是一件困难的事情。其实大家可以很清楚的看出来,这个世界上,开源项目不计其数,但真正能让人知道的少之又少,许多开源项目在开始后没多长时间便死去了。在国内论坛中,很多开源项目的发起者都在抱怨,开源环境很差,没有人参与他们的项目。当然,这其中也有项目本身吸引力的因素。其实,做开源是需要一些理想主义的,这样,才能在一条未知的路上前行。XRuby中也存在类似的问题,许多参与者一开始总是兴致勃勃的要求加入,好一些的,贡献了一些代码之后,便很长时间没有声音,有的则在加入之后,一行代码都没有写,便无声无息了。从加入开始一直比较稳定的贡献代码的人,屈指可数。不过,从另一个角度,这也说明了,当一个开源项目具备了一定的生命力之后,并不会因为某个人的不作为而死去。不管一路上有多少阳光和风雨,XRuby走过了它的第一个生日,步入了第二个年头,大家已经开始尝试着进行Rails的支持,我们会努力让它走得更好。在班加罗尔讲XRuby时,有人问过我,现在XRuby面临的主要问题是什么,我说,我们没有足够的资源。其实,现在可以看到的很多问题对我们来说,并不是非常困难,但却需要投入大量时间来完成。这也是我们始终如一的欢迎有兴趣的人加入我们的原因。如果你愿意和XRuby一起成长,欢迎加入我们!







