某常跟我提到了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改写后,在我的路由上跑,测试结果如下:
Lang | Time |
---|---|
lua 5.1.4 | 0.06s |
mruby | 0.07s |
mruby-iij | 0.12s |
ruby 1.9.2p0 | 2.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,结果如下:
Lang | Time |
---|---|
python 2.7.3 | 16.549s |
pypy 1.8.0 | 3.642s |
lua 5.1.4 | 7.205s |
luajit 2.0.0-b9 | 3.678s |
node.js 0.6.12 | 3.851s |
ruby 1.9.3p327 | 16.056s |
ruby 1.9.3p327(mruby版) | 15.888s |
mruby | 21.811s |
mruby-iij | 23.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究竟想干嘛?有点看不出来的说。
2 条评论 :
可以解釋成向 lua 挑戰,但能力有點不足嗎? XD
有jit的3个都很牛逼啊
不过mruby看起来光要达到clua的水准看起来都远未够班呢
发表评论