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

luikore · 发布于 2014年12月27日 · 最后由 lmorenbit 回复于 2014年12月31日 · 1937 次阅读
2880

经常有这样的代码

array.sort_by(&:date).take 3

现在可以这么写:

array.min_by 3, &:date

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

共收到 21 条回复
60
googya · #1 · 2014年12月27日

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'

2880
luikore · #2 · 2014年12月27日

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

2.2 有这么一行:

if (!NIL_P(num))
    return nmin_run(obj, num, 1, 0);
96
bhuztez · #3 · 2014年12月27日

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

2880
luikore · #4 · 2014年12月28日

#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 这个优化, 只是实测没什么速度变化...

96
bhuztez · #5 · 2014年12月28日

#4楼 @luikore

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

Lua 那样利用乱序执行

Lua为啥要乱序执行?

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

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

2880
luikore · #6 · 2014年12月29日

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

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

96
bhuztez · #7 · 2014年12月29日

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

2880
luikore · #8 · 2014年12月29日

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

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

96
leozwa · #9 · 2014年12月30日

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

96
bhuztez · #10 · 2014年12月30日

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

2880
luikore · #11 · 2014年12月30日

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

2880
luikore · #13 · 2014年12月30日

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

96
lmorenbit · #14 · 2014年12月30日

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

96
bhuztez · #16 · 2014年12月30日

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

2466
rasefon · #17 · 2014年12月30日

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

2880
luikore · #18 · 2014年12月30日

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

2466
rasefon · #19 · 2014年12月30日

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

2880
luikore · #20 · 2014年12月30日

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

96
lmorenbit · #21 · 2014年12月31日

#15楼 @luikore ok 谢谢

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