`
RednaxelaFX
  • 浏览: 3038585 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

Ruby 1.8.x中复合赋值运算符的实现

阅读更多
嗯,在RPGCHINA读帖的时候看到一个有趣的主题,说Ruby的a = a + 1与a += 1的执行效率不一样。很明显这个认识有偏差,事实上Ruby的复合赋值运算符与其展开的简单赋值形式在经过解释器前端的解析后就一模一样了。连对应的抽象语法树都是一样的,执行效率能差多少呢?

更糟糕的是回帖中有错误的解释,将这个“差异”对应到汇编上:
引用
引用第8楼nightaway于2008-03-12 21:33发表的  :
a+=1   编译后的汇编指令  add
a++     编译后的汇编指令  inc
inc 的运算周期 小于 add 所以 a++ 比 a+=1 快

如果楼主有疑问可以学学汇编语言.. 这样你的编码水平会大增..

这不是答非所问么……那帖的主题明明是在讨论a += 1与a = a + 1的差异;+=与++又不等价,况且Ruby里连++运算符都没有 =_=|||
引用
引用第12楼jiangcq于2008-05-26 10:32发表的  :
回答正确,+= 比 =    +    效率要高很多,特别是在早期的计算机上
####################
a=a+1
MOV  EAX,A
MOV  EBX,1
ADD  EAX,EBX
MOV  A,EAX
##################
a+=1
MOV  EAX,A
INC  EAX
MOV  A,EAX
####################
如果没记错的话应该是这样计算的

诶,所以说不知道就不要乱说嗯。这MOV EBX, 1在运算支持立即数的指令集里明显是废的。
编译器能做的优化多的是。偏偏这a = a + 1却没什么优化可做。假如这是C代码生成为x86的目标代码,编译器选择ADD r32, #1还是INC r32与这个语句写成a = a + 1还是a += 1没什么关系……要说跟a++或者++a扯上关系那还靠谱点,不过如果是好的优化编译器都应该能生成一样的代码。
这复合赋值运算符最大的意义在于:1、让源代码更加简洁易读;2、减少重复的地址计算。之所以a = a + 1与a += 1无论如何也差不了多少是因为a本身就已经是一个变量了,能“直接”访问。假如这是一个需要昂贵的地址计算的表达式,那复合赋值运算符就显得很有意义了。例如这样:
a.prop[0][1] = a.prop[0][1] + 1;

这里要赋值的目标的地址无法直接得到,得经过复杂的计算。这种情况下如果编译器不优化、又或者计算地址有副作用,则取值和赋值分开两次来计算地址就比只计算一次要慢一些,因而有优化的必要。

原帖地址:http://www.rpgchina.net/read-htm-tid-32257.html

要讨论这种问题果然还是得研究一下MRI到底是如何实现这些东西的。
YARV的实现机制比较不同,而且1.9.0这个实验系列何时才会进化到稳定版的2.0.x还很难说,所以还是拿1.8.x系列为准来讨论了。

===========================================================================

下面内容是我在那帖14楼的回复:


这个……事情要具体问题具体分析对吧,张冠李戴就不太好了 ^ ^
+=之类的复合赋值运算符在许多语言都有,语义类似但是实现的方式并不总相同。
RGSS里的脚本语言是Ruby,RPG Maker VX里使用的Ruby是1.8.1版的MRI。Ruby源代码并没有被直接编译到机器码,而是被Ruby解释器所解释:先把源代码翻译成抽象语法树,然后解释抽象语法树。
在Ruby里,一切皆是对象。因此像是加号减号之类的运算,也被看作是对象上的方法。a += 1的语义是a = a.+(1)(语义是:调用a对象上的+()方法,把1作为参数传进去,然后将方法的返回值赋值给a。更准确的说,是对右手边的a对象发送了一个"+"消息,以1为参数;返回得到的值赋值给左手边的a)。
+=的语义不是单独定义,而是由+()方法所决定的;换句话说一个类定义了+()方法就自动具备了+=。假如有语句a = 1,那么a是一个Fixnum,+=当中调用的+()方法就是Fixnum#+()。

先看看"+="这个符号被解析器识别为什么了。Ruby的扫描器(词法分析器)里有这么一段:
parse.y
case '+':
c = nextc();
if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) {
    lex_state = EXPR_ARG;
    if (c == '@') {
        return tUPLUS;
    }
    pushback(c);
    return '+';
}
if (c == '=') {
    yylval.id = '+';      // 注意这里,id是'+'
    lex_state = EXPR_BEG;
    return tOP_ASGN;      // 然后整体以tOP_ASGN返回
}

可以看到+=被识别为tOP_ASGN类型的token。

a += 1形式的语句对应的这条语法:
statement: //...
    | var_lhs tOP_ASGN command_call
    // ...
    | //...
    ;

语法对应着解析器(语法分析器),而解析器会生成抽象语法树。如果等号前的是||则语法生成NODE_OP_ASGN_OR节点,如果是&&则生成NODE_OP_ASGN_AND节点,其它则调用call_op()函数生成NODE_CALL节点。
parse.y
var_lhs tOP_ASGN command_call
{
    value_expr($3);
    if ($1) {
        ID vid = $1->nd_vid;
        if ($2 == tOROP) {
            $1->nd_value = $3;
            $$ = NEW_OP_ASGN_OR(gettable(vid), $1);
            if (is_asgn_or_id(vid)) {
                $$->nd_aid = vid;
            }
        }
        else if ($2 == tANDOP) {
            $1->nd_value = $3;
            $$ = NEW_OP_ASGN_AND(gettable(vid), $1);
        }
        else {
            $$ = $1; // 获得var_lhs对应的节点
            // call_op将返回一个NODE_CALL节点,并赋值给var_lhs对应节点的“值”
            $$->nd_value = call_op(gettable(vid),$2,1,$3);
        }
    }
    else {
        $$ = 0;
    }
}

由于节点的“值”(nd_value)被赋值为一个NODE_CALL节点,这里实质上完成了将a += 1变为a = a.+(1)的转换。

看看a = a + 1对应的语法和动作:
parse.y
lhs '=' command_call
{
    $$ = node_assign($1, $3);
}

结合下面node_assign()函数的实现,可以看到这里是把右手边的节点赋值给了左手边节点的“值”(nd_value)。并且,右手边的a + 1对应的语法与动作如下:
parse.y
arg '+' arg
{
    $$ = call_op($1, '+', 1, $3);
}

也是调用call_op()生成NODE_CALL节点,跟前面a += 1时一样。
于是,a += 1与a = a + 1在被解析后所生成的语法树是一样的,后续执行中就都是等价的了。
parse.y
static NODE*
node_assign(lhs, rhs)
    NODE *lhs, *rhs;
{
    if (!lhs) return 0;

    value_expr(rhs);
    switch (nd_type(lhs)) {
      case NODE_GASGN:
      case NODE_IASGN:
      case NODE_LASGN:
      case NODE_DASGN:
      case NODE_DASGN_CURR:
      case NODE_MASGN:
      case NODE_CDECL:
      case NODE_CVDECL:
      case NODE_CVASGN:
        lhs->nd_value = rhs; // 注意这里
        break;

      case NODE_ATTRASGN:
      case NODE_CALL:
        lhs->nd_args = arg_add(lhs->nd_args, rhs);
        break;

      default:
        /* should not happen */
        break;
    }

    return lhs;
}


(nd_value是在node.h里定义的一个宏,展开为u2.node)

===========================================================================

这是Fixnum#+()对应的C函数:
numeric.c
/*
 * call-seq:
 *   fix + numeric   =>  numeric_result
 *
 * Performs addition: the class of the resulting object depends on
 * the class of <code>numeric</code> and on the magnitude of the
 * result.
 */

static VALUE
fix_plus(x, y)
    VALUE x, y;
{
    if (FIXNUM_P(y)) {
	long a, b, c;
	VALUE r;

	a = FIX2LONG(x);
	b = FIX2LONG(y);
	c = a + b;
	r = LONG2NUM(c);

	return r;
    }
    if (TYPE(y) == T_FLOAT) {
	return rb_float_new((double)FIX2LONG(x) + RFLOAT(y)->value);
    }
    return rb_num_coerce_bin(x, y);
}

该函数被注册到Ruby的类型系统中:
numeric.c
rb_define_method(rb_cFixnum, "+", fix_plus, 1);

rb_cFixnum是Ruby的Fixnum的C的实现类,继承自rb_cInteger:
numeric.c
rb_cFixnum = rb_define_class("Fixnum", rb_cInteger);

上面rb_define_method函数使得fix_plus与一个NODE_CFUNC关联在了一起。这个函数会调用rb_intern(name)来将方法名转换为ID,这里对运算符做了特殊处理:
parse.y
if (name[0] != '_' && ISASCII(name[0]) && !ISALNUM(name[0])) {
    /* operators */
    int i;

    for (i=0; op_tbl[i].token; i++) {
        if (*op_tbl[i].name == *name &&
            strcmp(op_tbl[i].name, name) == 0) {
            id = op_tbl[i].token;
            goto id_regist;
        }
    }
}

这个特殊处理可以保证运算符与内建函数的对应关系。

P.S. 以上代码来自Ruby 1.8.7的源码。
P.P.S Ruby Hacking Guide真是本好书

===================================================================

“不幸”的是,JRuby的行为也是一样。我们可以通过JRuby.parse方法查看一段代码被解析成了怎样的AST:
irb(main):001:0> require 'java'
=> true
irb(main):002:0> JRuby.parse 'a += 1'
=> RootNode
  NewlineNode
    LocalAsgnNode |a| &0 >0
      CallOneArgFixnumNode |+|
        LocalVarNode |a| &0 >0
        ArrayNode
          FixnumNode ==1
irb(main):003:0> JRuby.parse 'a = a + 1'
=> RootNode
  NewlineNode
    LocalAsgnNode |a| &0 >0
      CallOneArgFixnumNode |+|
        LocalVarNode |a| &0 >0
        ArrayNode
          FixnumNode ==1

可见复合赋值表达式与分开的表达式被解析为相同的AST。
12
0
分享到:
评论

相关推荐

    ruby-irb-1.8.7.352-13.el6.x86_64.rpm

    ruby-irb-1.8.7.352-13.el6.x86_64.rpm ruby-irb-1.8.7.352-13.el6.x86_64.rpm

    ruby-1.8.7.352-13.el6.x86_64.rpm

    ruby-1.8.7.352-13.el6.x86_64.rpm ruby-1.8.7.352-13.el6.x86_64.rpm

    ruby-libs-1.8.7.352-13.el6.x86_64.rpm

    ruby-libs-1.8.7.352-13.el6.x86_64.rpm ruby-libs-1.8.7.352-13.el6.x86_64.rpm

    utf8:用于 Ruby 1.8.x 的轻量级 UTF8-aware String 类

    用于 Ruby 1.8.x 的轻量级 UTF8-aware String 类扫描代码是用 C 实现的(如果你们中有人想帮忙的话,也会喜欢 Java 版本)。 目前,此 gem 在 1.8.7、1.9.2、1.9.3 和 Rubinius 上进行了测试 - 它可能适用于其他人,...

    SmartAdmin1.8.7.5的RubyOnRails版本

    SmartAdmin在Ruby on Rails中的实现可能包括了预处理过的CSS和JS文件,可能已经配置好了Webpacker或Sprockets来处理前端资源,以及Bootstrap和其他前端库的集成。此外,它可能还提供了示例布局、小部件、图表和其他...

    ruby 1.8 API

    ruby 1.8 API; 包括ruby的核心内容.

    jQueryWidgetFactory1.8.21开发指南.doc

    ### jQuery Widget Factory 1.8.21 开发指南 #### 概述 jQuery Widget Factory 是 jQuery UI 提供的一个强大的工具包,它简化了自定义控件的开发过程。通过这个框架,开发者可以轻松地创建出功能丰富且高度可定制...

    02Ruby 运算符.docx

    Ruby中的赋值运算符不仅包括基本的赋值运算符(`=`),还包括一系列复合赋值运算符,这些运算符将运算和赋值结合在一起,提高了代码的可读性和效率。 - **简单赋值运算符 (`=`)**:将右侧操作数的值赋给左侧操作数。 ...

    Exercism-exercises-in-Ruby.-ruby.zip

    Exercism_exercises_in_Ruby._ruby.zip Exercism_exercises_in_Ruby._ruby.zip Exercism_exercises_in_Ruby._ruby.zip Exercism_exercises_in_Ruby._ruby.zip Exercism_exercises_in_Ruby._ruby.zip Exercism_...

    Ruby编程语言_涵盖Ruby 1.8和1.9

    Ruby 1.8和1.9是Ruby语言的两个重要版本,它们在许多方面有所不同,同时也对Ruby的发展产生了深远的影响。 Ruby 1.8是Ruby的一个早期版本,发布于2004年,它引入了许多特性,如块语法的改进、元编程能力的增强以及...

    RUBY+1.8 windows UI

    在使用Ruby 1.8进行开发时,开发者可以利用MRI(Matz's Ruby Interpreter)作为默认解释器,也可以选择JRuby(基于Java平台的实现)或Rubinius(使用LLVM作为后端的实现)来获取不同的性能特性。Ruby 1.8版本虽然已...

    七牛RubySDK.zip

    此 Ruby SDK 适用于 Ruby 1.8.x, 1.9.x, jruby, rbx, ree 版本,基于 七牛云存储官方API 构建。使用此 SDK 构建您的网络应用程序,能让您以非常便捷地方式将数据安全地存储到七牛云存储上。无论您的网络应用是一个...

    rh-ruby23-rubygem-json-1.8.3.1-70.el7.x86_64.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    rh-ruby23-rubygem-json-1.8.3.1-67.el7.x86_64.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    ruby 1.8.5 .tgz

    Ruby 1.8.5 是 Ruby 1.8 系列的一个稳定版本,相比于更早的版本,它引入了一些改进和修复,为开发者提供了更稳定的编程环境。 1. **对象导向**:Ruby 是一种纯面向对象的语言,每个值都是一个对象,包括基本类型如...

    ruby-1.8.5.tar

    Ruby 1.8.x 系列是 Ruby 的一个长期支持版本,尽管已经过时,但在某些老项目或特定环境中仍然不可或缺。 在 "ruby-1.8.5.tar" 压缩包中,我们可以预期包含以下内容: 1. **源代码**:所有 Ruby 解释器的 C 语言源...

    ruby-1.8.7.tar.gz

    Ruby 1.8.7 是一个古老的 Ruby 语言版本,它是 Ruby 社区在 2011 年发布的最后一个 1.8.x 系列版本。这个版本在当时非常流行,尤其对于某些项目和框架来说是必要的依赖,比如 Redmine 就是一个典型例子。Redmine 是...

    Ruby 解压版所缺少的DLL文件(window下有用)

    解压版的RUBY开发环境(SDK),在windows下运行时,默认的没有这些dll,把找到的现在打包到一块,解压后放在ruby安装目录的bin目录下即可。 主要有如下: readline.dll、zlib.dll、ssleay32.dll、iconv.dll、libeay...

Global site tag (gtag.js) - Google Analytics