Ruby Ruby 2.2 Tips: max (n), max_by (n), min (n), min_by (n)

luikore · 2014年12月27日 · 最后由 lmorenbit 回复于 2014年12月31日 · 4716 次阅读

经常有这样的代码

array.sort_by(&:date).take 3

现在可以这么写:

array.min_by 3, &:date

其实现不需要全排序和额外分配一个中间数组,速度往往更快

new way 但是方法实现中,1.9 和 2.2,是一样的

2.2

static VALUE
enum_min_by(VALUE obj)
{
    VALUE memo[2];

    RETURN_ENUMERATOR(obj, 0, 0);

    memo[0] = Qundef;
    memo[1] = Qnil;
    rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
    return memo[1];
}


a = [1, 2, 3, 4, 3, 2, 1, 0, -9, 387]

a.min_by 3, &:to_i
#=> [-9, 0, 1]

1.9

static VALUE
enum_min_by(VALUE obj)
{
    VALUE memo[2];

    RETURN_ENUMERATOR(obj, 0, 0);

    memo[0] = Qundef;
    memo[1] = Qnil;
    rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
    return memo[1];
}

a = [1, 2, 3, 4, 3, 2, 1, 0, -9, 387]

a.min_by 3, &:to_i
ArgumentError: wrong number of arguments(1 for 0)
from (pry):3:in `min_by'

#1 楼 @googya 因为你用的 pry-doc, 那个还没有加 2.2 的文档

2.2 有这么一行:

if (!NIL_P(num))
    return nmin_run(obj, num, 1, 0);

这个意义不大吧。还不如把 Ruby 换成 register-based VM

#3 楼 @bhuztez 呃,register based VM 字节码占内存多。如果像 MRuby 那样用压缩的字节码跑 register based VM, 那执行每个指令都要经历一个 decode 过程,会产生不少消耗。现在完全用 C 实现的 Ruby VM 是很难像 Lua 那样利用乱序执行特性达到比较高执行效率的。

register based VM 的 register 是虚拟寄存器,在物理寄存器分配上也不一定有优势... stack based VM 也有很多利用物理寄存器技巧,例如把 stack 顶部 (经常 push pop 的内容) spill 到物理寄存器上,Ruby 源代码里改编译参数也能 enable 这个优化,只是实测没什么速度变化...

#4 楼 @luikore

目前纯解释执行比较快的 PHP 和 Erlang 都是 register based。register based VM 虽然单条指令长了,但是指令数量少了,最终大小差不多的。

Lua 那样利用乱序执行

Lua 为啥要乱序执行?

register based VM 的 register 是虚拟寄存器,在物理寄存器分配上也不一定有优势...

x86 根本就没啥寄存器可以分配。register-based 也没必要非得分配在物理寄存器上。

#5 楼 @bhuztez -_- zend 运行时就是个呵呵吧... register 机在函数调用上还是得和 stack 机一样压栈,所以很多地方区别也没这么大。register 机字节码必然比 stack 机占空间多是公认的经验,因为很多连续运算的"中间数", 就是和栈高度吻合的,其中的 push/pop 指令会少很多,然后字节码占空间就大幅减小了。register 机设计得比较好可以只多 25% 左右,如果你有设计巧妙的 register 机指令集使得字节码可以小到和栈机差不多,求共享。

利用 OOO 是 LuaJIT 的解释器的汇编实现里有考虑减少指令之间的 bubble 的考虑 (还有先 decode 下条指令再 jump 也是这个目的)

#6 楼 @luikore 我看 Erlang 就不压栈啊,Erlang 直接寄存器传参

#7 楼 @bhuztez 寄存器机那个是先把 call 参数准备好,放到连续的寄存器里,指令指定第一个参数的寄存器号,有几个参数,然后执行的时候把参数拷贝到 stack frame 中去,和栈机把参数压栈,然后执行的时候移动一下参数没有什么本质不同。寄存器机那个参数也和压栈一样需要顺序连续放置,需要一些 mov 指令调整,而且 mov 指令还要加个寄存器号比 push 命令多一个操作数。

最终出来的寄存器机 stack frame layout 和栈机没什么不同

请管理员颁发年度最佳歪楼奖...

#8 楼 @luikore 那你说 PHP 跑分那么快是怎么回事?

#10 楼 @bhuztez 你看的是哪个?我来告诉你什么回事

#12 楼 @bhuztez 现在几乎都不用 32 位机器了,看 64 位的结果啊 http://benchmarksgame.alioth.debian.org/u64/compare.php?lang=yarv&lang2=php 而且那个 pidigits 的 Ruby 实现本来就有很大问题。而且类似这些 benchmark 的循环套循环代码在做网站中不还都是用 C 库搞的...

@luikore 有没有一些整体的文章或者书来介绍一下 ruby 2.2 呢?

#13 楼 @luikore 不知道为啥最近都赶上来了,之前都离 PHP 好远呢

语法糖没意思,我最不喜欢 ruby 一种功能多种写法,还不如多想点办法提高运行效率。

#17 楼 @rasefon 这个不是语法糖,而且背后的算法不一样,比 sort 然后 take 的执行效率高

#18 楼 @luikore 纯算法上的解法的话,排序 + 取前 n 个本来就不是好的做法吧。

#19 楼 @rasefon 对啊,这不是一个功能的另一种写法,而是增加了一个新功能,而且可以替代掉常见的不好的做法

需要 登录 后方可回复, 如果你还没有账号请 注册新账号