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究竟想干嘛?有点看不出来的说。

2012年11月30日星期五

OpenWrt Attitude Adjustment

换上12.09差不多两个月了吧,各方面都很满意。尤其是近几天折腾了一下RTD1186,更觉得有完整的第三方支持是多么幸运多么可贵的一件事。

这是我的补丁包:https://www.boxcn.net/s/ybou0cl9yekzd2x7e5mh

基于svn://svn.openwrt.org/openwrt/branches/attitude_adjustment,r34080,没改多少东西:

./nls-cp936.patch
./package/exfat-utils/Makefile
./package/exfat-utils/src/Makefile
./package/exfat/Makefile
./package/exfat/src/Makefile
./toolchain/uClibc/patches-0.9.33.2/999-posix_fallocate.patch
./feeds/packages/net/aria2/Makefile
./feeds/packages/net/autossh/files/autossh.config
./feeds/packages/net/autossh/files/autossh.init
./feeds/packages/net/openssh/patches/999-env-pwd.patch
./feeds/packages/net/vsftpd/patches/999-ssl.patch
./feeds/packages/net/vsftpd/Makefile
./target/linux/ar71xx/patches-3.3/999-tl-wr941nd-usb.patch

除了nls-cp936.patch需要手工打补丁外,其他文件覆盖即可。

busybox

busybox的中文显示需要特别配置一下,首先是打开unicode支持,其次要将"Range of supported Unicode characters"的值改为0,"Allow wide Unicode characters on output"改为yes。另外vi也可以通过调整配置使之能够显示中文,但也仅仅是显示而已,输入和删除的时候仍然会遇到光标的问题。

nls-cp936.patch

增加nls_cp936.ko,mount的时候可能会用到。

exfat、exfat-utils

虽然放在这里,但不知道是usb供电不足还是代码稳定性不佳,总之我不建议挂OpenWrt上的移动硬盘使用exfat。ntfs我也试过了,跟exfat一样,最后我用的还是ext3。

uClibc

增加了fallocate,据说对samba的性能有帮助,据说。

aria2

OpenWrt挂迅雷离线就靠他了。

autossh、openssh

让openssh可以从环境变量中获得登录密码,出墙需要用,详细说明见之前写的《OpenWRT AutoSSH》

vsftpd

打开ftps支持。如果要给远端共享文件的话,或许用得上。内网还是不要用了,路由的cpu扛不住,会导致速度下降很多。

999-tl-wr941nd-usb.patch

我的路由改了个usb口出来,官方的代码没有提供支持。

2012年11月28日星期三

RTD1186折腾记之修改根分区格式

这个盒子是一个朋友买来之后觉得没用暂时放我这里的,Realtek RTD1186方案,主频750MHz,内存512M,闪存4G。单就硬件而言,比我的路由强很多,只不过没有OpenWrt之类完整的第三方支持,所以干什么都需要自己来。

本地端adb connect 192.168.1.186,然后打开adb putty,Host写transport-any,Port保持5037,居然就这么连进去了。固件本身是已经root了的,倒是少了很多麻烦。只是随便乱逛了一下之后,我有一种头痛的感觉。

从/和/system看来,这像是个标准的Android,只是目录结构和文件各种乱。根目录下居然放了三个内核模块。/system/rtk_rootfs/usr/local/bin/下面有个44.1M大的opt2.8.tar。/system/rtk_rootfs/bin/opt和/system/rtk_rootfs/usr/local/bin/opt下各有一套optware,区别仅仅是前者比后者多了两个可执行文件。至于man、include一类,我都不想提了…

想要删改,第一个问题是/system的分区格式。原厂用的是只读的squashfs,随便改点什么都要刷一次系统,这也太累了,改掉。

刷固件并不需要ttl,但是为了能看见自己都干了啥,ttl还是需要的。

将盒子拆开,电路板上只有一个六针的接口是空着的,标注是J1到J6。用万能表在断电的时候测针脚与高频头外侧之间的电阻,J1是0Ω,J4和J5绝缘,其他三针不为0。上电再测试直流电压,J1为0V,J2为2.8V,J3示数不稳定,J6为3.3V。这应该就是ttl针脚了,J1是GND,J2是RX,J3是TX,J6是VCC。

在最终达到目的之前,我遇到了蛮多问题的,也花了不少时间,这里就不写了,直接上结果。

从ttl看到的启动信息里有这么一段:

One H27UBG8T2A chip has 1 die(s) on board
nand part=H27UBG8T2A, id=add7949a, device_size=4294967296, chip_size=4294967296, num_chips=1, page_size=8192, isLastPage=1, eccBits=24

H27UBG8T2A就是闪存芯片的型号,Page尺寸上面就有,8192字节。以型号为关键字搜到了一个pdf技术文档,其中有写OOB的尺寸,448字节。这两个数据在制作img的时候需要。

从官方下回来的install.img中把mkyaffs2image和squashfs1.img解出来,然后:

unsquashfs squashfs1.img
mv squashfs-root system
mkyaffs2image -f -c 8192 -s 448 ./system yaffs2_root.img

改名的那一步不是多余的,mkyaffs2image那一句的./也不是多余的,神奇的设计。

虽说squashfs是压缩过的,可192M的img转换成yaffs2就膨胀到1.2G,可见官方的固件里有多少垃圾。不过这跟8K的Page尺寸也有关系,这块芯片显然更适合用在DC一类的设备里,当系统盘存小文件实在太浪费空间了。

将原厂install.img里的squashfs1.img替换为自己做的yaffs2_root.img,打开configuration.xml并修改:

--- configuration.xml.orig
+++ configuration.xml
@@ -40,9 +40,10 @@
                 <fileName>package5/bootloader.tar</fileName>
                 <version>N/A</version>
             </image>
-            <image type="squash">
-                <fileName>package5/squashfs1.img</fileName>
+            <image type="yaffs2">
+                <fileName>package5/yaffs2_root.img</fileName>
                 <mountPoint>/</mountPoint>
+                <sizeBytesMin>2147483648</sizeBytesMin>
             </image>
             <image type="yaffs2">
                 <fileName>package5/yaffs2_2.img</fileName>

我把新的/system设置为2G,这样/data还有1.7G。其实MIPS跑安卓压根没什么用,/system再改大点也没关系。

把改好的install.img放到一个U盘的根目录下,按住盒子前面板的reset,或连上ttl的时候按住空格键上电,开始刷固件。bootloader会自动构造传递给内核的参数,原厂init会依序尝试squashfs和yaffs2,所以没什么要改的了。刷完之后会自动重启,挂载分区处不知道为什么需要花很长时间,除此之外一切正常,/system已经变成可写的yaffs2了。