2012年12月14日星期五

A Quick Preview of mruby

某常跟我提到了mruby,正好之前从来没接触过,于是弄回来看了看。我的主要目的是寻找一种在OpenWrt之类受限环境下性能不错且易用性更好的lua替代品,以下是一些初步感受。

功能性

mruby的readme里有这么一句:

mruby is the lightweight implementation of the Ruby language complying to (part of) the ISO standard

看看ISO/IEC 3017:2012就可以发现,ruby标准包含的内容很少,而mruby仅仅是标准的part of,可以用的东西就更少了。

目前的mruby没有标准中包含的Regexp、IO和File,不是标准的Socket和OpenSSL也没有,Net当然就更没有了。难怪某常说mruby有些“太小”,这小得稍微都有些过分了…

iij维护了一个加强版的fork,Regexp和IO都可以用了,不过Net仍然是没有。在mruby-curl一类的东西出现之前,恐怕只能用system去调用外部命令了吧。

编译

mruby编译起来很容易,不管是mingw还是linux,即使是iij的fork加上mruby-sqlite3,编译起来也不困难。交叉编译则要稍微麻烦一点,需要先编译出一个x86版本,然后再修改Makefile。总的来说编译难度不高,即使是交叉编译,只要对Makefile略有了解的人都可以完成。

扩展性

matz说了,mruby里不会有require。个人认为,这实在是一个馊主意。将所有代码都编译进同一个目标文件,不管是加载时间还是内存占用都会增加。倘若某个闭源程序使用mruby作为内嵌脚本语言,用户想要扩充功能也是不可能的了。

iij的fork有require,只是暂时没看见有什么可以使用这一特性的扩展。希望iij可以长期维护他的fork吧,真心觉得matz有时候挺昏庸的。

性能

之前写过一个spider,用来爬新浪微博的内容,前端是一个用lua写的cgi,从sqlite3数据库中读出内容生成json并输出。将其用mruby改写后,在我的路由上跑,测试结果如下:

LangTime
lua 5.1.40.06s
mruby0.07s
mruby-iij0.12s
ruby 1.9.2p02.21s

因为不是什么严谨的测试,代码我就不贴了。单就这一个任务而言,mruby和lua的速度差不多。mruby-iij的可执行文件是835.2K,mruby的则是489.2K,速度比和体积比都是大约1.7,可见可执行文件的体积的确会对执行速度产生影响。至于cruby,实在是有点太慢了。

接着我跟某胖子要了一段数值运算的代码,打算试试PC上跑起来的结果又是如何。这是某胖给我的python版:

from math import *

def getPrimes(up):
    p=[True]*(up+1)
    for i in range(4,up+1,2):
        p[i]=False
    for i in range(3,int(sqrt(up))+1,2):
        if p[i]:
            j=i
            while True:
                j+=i
                if j>=up:
                    break
                p[j]=False
    return [i for i in range(2,up+1) if p[i]]

getPrimes(31415927)

lua版:

function getPrimes(up)
    local p = {}
    for i=1,up do
        p[i]=false
    end
    for i=4,up,2 do
        p[i]=true
    end
    for i=3,math.floor(math.sqrt(up)),2 do
        if p[i] then
            local j = i
            while true do
                j=j+i
                if j>=up then
                    break
                end
                p[j]=false
            end
        end
    end
    local result = {}
    for i=2,up do
        if p[i] then
            table.insert(result,i)
        end
    end
    return result
end

getPrimes(31415927)

我改的mruby版:

def primes(up)
    a = [true] * (up+1)

    i = 4
    while i <= up do
        a[i] = false
        i += 2
    end

    n = Math.sqrt(up).to_i
    i = 3
    while i <= n do
        if a[i] then
            j = i
            while true do
                j += i
                break if j >= up
                a[j] = false
            end
        end
        i += 2
    end

    r = []
    2.upto(up) { |i| r.push(i) if a[i] }
    return r
end

primes(31415927)

我改的ruby版:

def primes(up)
    a = Array.new(up+1, true)

    (4..up).step(2) { |i| a[i] = false }

    (3..Math.sqrt(up).to_i).step(2) do |i|
        next if not a[i]
        j = i
        a[j] = false while (j += i) < up
    end

    r = []
    2.upto(up) { |i| r.push(i) if a[i] }
    return r
end

primes(31415927)

我改的node.js版:

function primes(up) {
    var i = up + 1;
    var a = new Array(i);
    while (--i >= 0) a[i] = true;

    for (i=4;i<=up;i+=2) a[i] = false;

    for (i=3;i<=Math.floor(Math.sqrt(up));i+=2) {
        if (!a[i]) continue;
        j = i;
        while ((j += i) < up) a[j] = false;
    }

    var r = [];
    for (i=2;i<=up;i++) {if (a[i]) r.push(i);}

    return r;
}

primes(31415927);

测试环境为Ubuntu 12.04,AMD Phenom II X4 955,结果如下:

LangTime
python 2.7.316.549s
pypy 1.8.03.642s
lua 5.1.47.205s
luajit 2.0.0-b93.678s
node.js 0.6.123.851s
ruby 1.9.3p32716.056s
ruby 1.9.3p327(mruby版)15.888s
mruby21.811s
mruby-iij23.044s

在PC上跑,磁盘IO对性能的影响比较小,所以以上结果应该更接近各种语言的真实水平。至少单就数组、循环和逻辑判断这几项的性能来说,mruby还差得远,连cruby都比不了,更不要说lua了。

另外还可以看出,mruby对功能的裁剪导致语言的表达能力下降了很多。其实只是少了Range的step而已,最后写出来的代码可读性和长度都比ruby版的要差。

Bug

up = 31415927
if ARGV[0] == 'new' then
    a = Array.new(up+1, true)
else
    a = [true] * (up+1)
end

以上两种数组初始化的方法在cruby中性能差不多,大概是0.115s和0.130s的样子,第一种要略快一点点。但在mruby里,第一种需要4.650s,第二种也要0.363s,明显有什么地方不对劲。

a = [0] * 11
i = 0
begin
    a[i] += 1
end while (i += 2) <= 10

b = [0] * 11
i = 0
b[i] += 1 while (i += 2) <= 10

p a
p b

cruby中,执行以上代码最后输出的两个数组内容是不一样的,区别在于第一种循环是先执行后判断,而第二种则是先判断后执行。mruby中,这两种循环的写法都是先判断后执行,明显mruby的处理有问题。

印象

其一,没有require绝对是昏招;其二,网络访问这么基础的功能都没有;其三,欠缺优化;其四,随便用用就让我碰上bug了…

ruby社区似乎没有Mike Pall这种monster,也没抱上Google一类的粗大腿,再加上我总觉得喜欢ruby的都是懒人,想让ruby快起来恐怕没那么容易。mruby究竟想干嘛?有点看不出来的说。