Archive

Author Archive

binary的常量优化

October 6th, 2009 Comments off

erlang的binary在这个网络程序里面占着非常重要的地位,所以otp团队采用了非常多的优化手段包括:
1. binary操作对应着 opcode
2. 根据生命期和作用,有4种类型的binary
3. hipe优化,把bs_操作直接翻译成asm指令
4. 编译器层面消除无必须的操作。

下面的例子就是演示4的特性:

yu-fengdemacbook-2:~ yufeng$ cat bin.erl
-module(bin).
-export([start/1]).

start(A)->
    B1= <<12>>,
    B2 = <<B1/binary, 5.0/float>>,
    B3= <<B2/binary,  "yes">>,
% B3的值是预先可以知道的, 无需一步步的构造

    <<"abcd", 3:32,B3:128/binary,_/binary>> = <<"abcd1234",A/binary,2:32, 8773:64, "a", 5.0/float>>.

% 2:32, 8773:64, "a", 5.0/float 这些都是预先知道的 直接翻译成二进制流。
yu-fengdemacbook-2:~ yufeng$ erlc +"'S'" bin.erl
yu-fengdemacbook-2:~ yufeng$ cat bin.S
{module, bin}.  %% version = 0

{exports, [{module_info,0},{module_info,1},{start,1}]}.

{attributes, []}.

{labels, 8}.


{function, start, 1, 2}.
  {label,1}.
    {func_info,{atom,bin},{atom,start},1}.
  {label,2}.
    {move,{integer,0},{x,1}}.
    {gc_bif,byte_size,{f,0},2,[{x,0}],{x,2}}.
    {bs_add,{f,0},[{x,1},{x,2},1],{x,1}}.
    {bs_add,{f,0},[{x,1},{integer,29},1],{x,1}}.
    {bs_init2,{f,0},{x,1},0,1,{field_flags,[]},{x,1}}.
    {bs_put_string,8,{string,"abcd1234"}}.
    {bs_put_binary,{f,0},{atom,all},8,{field_flags,[unsigned,big]},{x,0}}.

%% 一步到位
    {bs_put_string,21,
                   {string,[0,0,0,2,0,0,0,0,0,0,34,69,97,64,20,0,0,0,0,0,0]}}.

    {test,bs_start_match2,{f,3},[{x,1},2,0,{x,0}]}.
    {test,bs_match_string,{f,3},[{x,0},64,{string,[97,98,99,100,0,0,0,3]}]}.
%%  一步到位

    {test,bs_get_binary2,
          {f,3},
          [{x,0},
           2,
           {integer,128},
           8,
           {field_flags,[{anno,[8,{file,"./bin.erl"}]},unsigned,big]},
           {x,2}]}.
    {test,bs_skip_bits2,
          {f,3},
          [{x,0},
           {atom,all},
           8,
           {field_flags,[{anno,[8,{file,"./bin.erl"}]},unsigned,big]}]}.
    {test,is_eq_exact,
          {f,3},
          [{x,2},{literal,<<12,64,20,0,0,0,0,0,0,121,101,115>>}]}.
%%  一步到位

    {move,{x,1},{x,0}}.
    return.
  {label,3}.
    {badmatch,{x,1}}.


{function, module_info, 0, 5}.
  {label,4}.
    {func_info,{atom,bin},{atom,module_info},0}.
  {label,5}.
    {move,{atom,bin},{x,0}}.
    {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.


{function, module_info, 1, 7}.
  {label,6}.
    {func_info,{atom,bin},{atom,module_info},1}.
  {label,7}.
    {move,{x,0},{x,1}}.
    {move,{atom,bin},{x,0}}.
    {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

所以我们在使用binary的时候, 尽可能的利用这个特性。

Categories: Erlang探索 Tags:

如何用gdb调试erlang运行期(高级)

October 6th, 2009 Comments off

前些天在erlang的源码里面闲逛的时候发现了 bin目录下的cerl,一看原来是个调试的高级货。

我之前写过一篇文章http://mryufeng.javaeye.com/blog/113852 如何调试erlang 但是这是土八路的方法, cerl才是现代化工业。

# This is a script to start Erlang/OTP for debugging. PATH is set to
# include this script so if slave nodes are started they will use this
# script as well.
#
# usage: cerl [ OPTIONS ] [ ARGS ]
#
# The OPTIONS are
#
# -rootdir $MYROOTDIR
# Run an installed emulator built from this source
# -debug Run debug compiled emulator
# -gdb Run the debug compiled emulator in emacs and gdb.
# You have to start beam in gdb using “run”.
# -break F Run the debug compiled emulator in emacs and gdb and set break.
# The session is started, i.e. “run” is already don for you.
# -xxgdb FIXME currently disabled
# -purify Run emulator compiled for purify
# -quantify Run emulator compiled for quantify
# -purecov Run emulator compiled for purecov
# -gcov Run emulator compiled for gcov
# -valgrind Run emulator compiled for valgrind
# -lcnt Run emulator compiled for lock counting
# -nox Unset the DISPLAY variable to disable us of X Windows
#

要使用cerl 我们最好准备个调试版本的erlang。R13B 修正了些编译错误,可以编译出debug版本的系统:./configure && make TYPE=debug && make

这样就生成了个beam.debug的运行期。

我们要调试的时候 就可以在otp的binx目录下运行 cerl -debug -gdb -break main

这时候cerl自动会加载 emacs 启动 gud, 整个过程都是自动的。但是这个脚本有点小问题, gud模型下没有把源码和当前对应的调试对应起来。可以通过以下方式修正:

yu-fengdemacbook-2:bin yufeng$ diff cerl cerl2
284c284
<     exec $EMACS --eval "(progn (gdb \"gdb $EMU\") $gdbcmd)"
---
>     exec $EMACS --eval "(progn (gdb \"gdb --annotate=3  $EMU\") $gdbcmd)"

具体的操作和界面可以参照这篇文章:
http://www.nabble.com/printing-of-Eterm%27s-from-gdb-td19240493.html

在调试的时候 我们会希望查看 eterm的值,但是由于eterm的格式非常复杂, gdb的print什么的无法满足我们的需求。 otp开发团队于是开发出了一套方法来减轻我们的负担:

1. erts/etc/unix/etp-commands 这是gdb的脚本 包含了几十个etp方法,而且文档非常详细。

2. 源码里面的 pp, ps等函数, 这些函数是专门为gdb调试开发的。 可以用gdb的 call pp(xxx)来调用。

有了这些工具 调试和研究erts变成了一件很快乐的事情!

Categories: 网络编程 Tags:

Literal XML in Erlang with parse_transform/2

September 30th, 2009 Comments off

原文地址:http://hyperstruct.net/2007/6/26/literal-xml-in-erlang-with-parse-transform-2

One of the things I dislike about Erlang is that it severely impairs bragging opportunities. Yesterday I wrote a module that allows writing literal XML in the source and have it parsed into Erlang structures at compile time—sort of like E4X minus the manipulation goodies at runtime (at least for now).

You write:

Doc = '<greeting>Hello!</greeting>',
io:format("~p~n", [Doc]).

And it prints…

{xmlElement,greeting,
            greeting,
            [],
            {xmlNamespace,[],[]},
            [],
            1,
            [],
            [{xmlText,[{greeting,1}],1,[],"Hello!",text}],
            [],
            "/tmp",
            undeclared}

In most languages I’m familiar with, this would have granted the author instant Yacc-demigod status. With Erlang… it was less than 40 LOC. Hardly something you’d wear at a party.

Anyway, this code owes everything to Philip’s writings. It also uses parse_transform/2, and “programmers are strongly advised not to engage in parse transformations and no support is offered for problems encountered”. So unless you, like me, are still at the kid-in-a-candy-shop stage of Erlang experience, think twice before using this in production, ok?

The code is here http://repo.hyperstruct.net/inline_xml/

Categories: 网络编程 Tags:

erlang的abstract code

September 30th, 2009 1 comment

erlang的abstract code是编译的中间代码,很多工具如 erl_pp lint什么的都是根据这个做调整的。还有进一步的parse_transform也是基于它的。 所以,了解它非常重要。 erts user guide里面详细了描述了它的定义。我这里展示的是如何获取到某个模块的abstract code 以便进一步研究:

[root@localhost ~]# erl -s hello
Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

hello world
[root@localhost ~]# cat hello.erl
-module(hello).
-export([start/0]).

start()->
    io:format("hello world~n",[]).
[root@localhost ~]# erlc +debug_info hello.erl
[root@localhost ~]# erl
Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.3  (abort with ^G)
1> rp(beam_lib:chunks(hello, [abstract_code])).
{ok,{hello,[{abstract_code,{raw_abstract_v1,[{attribute,1,
                                                        file,
                                                        {"./hello.erl",1}},
                                             {attribute,1,module,hello},
                                             {attribute,2,export,[{start,0}]},
                                             {function,4,start,0,
                                                       [{clause,4,[],[],
                                                                [{call,5,
                                                                       {remote,5,{atom,5,io},{atom,5,format}},
                                                                       [{string,5,"hello world~n"},{nil,5}]}]}]},
                                             {eof,6}]}}]}}
ok
2>

对着文档开始好好分析吧。 Have fun!

Categories: 网络编程 Tags:

Latest News from the Erlang/OTP team at Ericsson September 5 2009

September 28th, 2009 Comments off

原文地址:http://erlang-factory.com/upload/presentations/167/KennethLundin-LatestnewsfromtheErlangOTPteamatEricssonErlang_Workshop2009.pdf

Coming Open Source releases

OTP R13B02 September 23

OTP R13B03 November 25

OTP R13B04 Jan-Feb 2010

OTP R13B05 ???

OTP R14B May-June 2010

OTP R14B01

OTP R14B02

Plans for later releases

Multicore performance improvements

– Delayed deallocation, let the right scheduler do it (R13B03)

– Improved handling of process table

– Separate allocators per scheduler

– Use NUMA info for grouping of schedulers

– Separate poll sets per scheduler (IO)

– Support Scheduler bindings, cpu_topology on Windows as well.

– Optimize Erlang applications in Erlang/OTP

– Fine grained parallelism, language and library functions.

– Better and more benchmarks

New way to build the documentation

– Using XSLTPROC to produce html, man and XSL-FO

– Using Apache FOP to produce PDF

Easier to interface C libraries and to make your own ”BIFs”

– Dynamically linked in BIF’s (for C-code , easier to write and more efficient

than drivers)

Support for validation in xmerl_sax_parser

BIFs for search in binaries (EEP-?)

红色的部分 好期待哦。。。

Categories: 网络编程 Tags:

节点间通讯的通道微调

September 23rd, 2009 10 comments

erlang节点间通讯是可以配置的,默认的是inet_tcp 。当2个节点要沟通的时候,net_kernel模块会负责建立必要的连接。 inet_tcp会调用底层的gen_tcp进行数据发送接受。 rpc或者节点间的消息交互都是通过这个port出去的。

在分布节点间,有时候会有大量的消息流动,那么所有的消息都是通过这个port出去 进来,所以这个port的性能极大的影响了节点间通讯的效率。那么有时候, 我们会想微调这个port的参数,根据业务的特点实现效率最大化,但是port如何得到呢?

node_port(Node)->
    {_, Owner}=lists:keyfind(owner, 1, element(2, net_kernel:node_info(Node))),
    hd([P|| P<-erlang:ports(), erlang:port_info(P, connected) == {connected,Owner}])

有了Port, 那么我们就可以设置tcp port的水位线,buffer等等。

inet:setopts(node_port('xx@nd-desktop'), [{high_watermark, 131072}]).

另外要注意 nodeup nodedown可能会换了个tcp链接 要注意重新获取。

还有另外一种方法,设置所有gen_tcp的行为, 比如以下方法:

erl -kernel inet_default_connect_options '[{sndbuf, 1048576}, {high_watermark, 131072}]'

但是这个影响面非常大, 影响到正常tcp的参数了。

Categories: Erlang探索 Tags: , ,

实验Erlang语法对应的opcode 让你对erlang理解更深

September 20th, 2009 2 comments

Erlang作为一门FP语言,和传统的语言结构一样, 有模块, 有函数, 有语句, 有判断, 有循环, 还有特别的模式匹配。 那么这些在底层是如何运作的。 我在底下给大家做个简单的实验,让大家一窥内部的细节,让大家写码的时候知道个大概。

erlang的VM作为register based的VM, 大概有400条指令.指令分为hot, normal, cold 3大类别。beam_emu.c是vm的实现,hot和cold指令在编译的时候 由脚本生成的,include到beam_emu去的。 hot是热门的操作如list, tuple操作, cold的就是比较偏的指令。

erlang的编译器支持生成汇编码, 让我们的研究成可能,具体用法是 erlc +”‘S'” m.erl
会生成m.S 这个汇编文件.

root@nd-desktop:~# cat gram.erl
-module(gram).
-export([start/1]).

start([X])->
   %% bif
    X1 = list_to_integer(atom_to_list(X)),

%% list
    W =[1,2,3],
    W1 = [4|W],

    K=[W1,9],

    %% constant fold
    A = 1 + 2,
   
  %% if
    B =
        if X1 + A > 0 -> 5;
           true -> 4
        end,

   %% case
    C =
    case B of
        {x, T} -> T;
        5 -> a1;
        3 -> a2;
        2 -> 1.0;
        other -> 2;
        true -> 3
    end,

   %% receive
    D =
    receive
        a1 ->
            2 + 1.2;
        2 -> 3;
        {tag, N}->N;
        a2 -> 5;
        _ -> ok
    after A ->
            timeout
    end,
   
    %% anon fun
    E = fun (1)-> D;
            (x)-> 2;
            (y)-> C;
            (<<"12">>)->1;
            (_) -> error
            end,

    F = E(D),

    %% fun
    G = f(B),

    io:format("~p~p~p~p~n",[F, G,W,K]),

    done.


f(1)-> 1;
f(2) ->2;
f(3) ->3;
f(4) ->4;
f(5) ->5;
f(x1) ->1;
f(x2) ->2;
f(x3) ->3;
f(x4) ->4;
f(x5) ->5;
f({x,1}) -> 1;
f({x,2}) ->2;
f({x,3}) ->3;
f({x,4}) ->4;
f({x,5}) ->5;
f(<<1:8, X:32, "xyz", F/float>>) -> {X, F};
f(_) -> err.
root@nd-desktop:~# erlc +"'S'" gram.erl
root@nd-desktop:~# cat gram.S
{module, gram}.  %% version = 0
{exports, [{module_info,0},{module_info,1},{start,1}]}.

{attributes, []}.

{labels, 45}. %%每个标签是跳转地址

%%每个指令对应这相应的opcode,在beam_emu中都可以找到。

{function, start, 1, 2}.
  {label,1}.
    {func_info,{atom,gram},{atom,start},1}.
  {label,2}.
    {test,is_nonempty_list,{f,1},[{x,0}]}.
    {get_list,{x,0},{x,1},{x,2}}.
    {test,is_nil,{f,1},[{x,2}]}. 
    {allocate_zero,2,2}.
    {move,{x,1},{x,0}}.
   %% bif调用
    {call_ext,1,{extfunc,erlang,atom_to_list,1}}.
    {call_ext,1,{extfunc,erlang,list_to_integer,1}}.
    %% 符号也是bif
   %% 3= 1 +2 const fold
    {gc_bif,'+',{f,3},1,[{x,0},{integer,3}],{x,1}}.
    %% if 语句是如此简单
    {test,is_lt,{f,3},[{integer,0},{x,1}]}.
    {move,{integer,5},{x,0}}.
    {jump,{f,4}}.
  {label,3}.
    {move,{integer,4},{x,0}}.
  {label,4}.
    {move,{x,0},{y,1}}.
   %% case语句同样是个if else的判断
   
    %% tuple是如何匹配的 效率高
    {test,is_tuple,{f,5},[{x,0}]}.
    {test,test_arity,{f,21},[{x,0},2]}.
    {get_tuple_element,{x,0},0,{x,1}}.
    {get_tuple_element,{x,0},1,{x,2}}.
    {test,is_eq_exact,{f,21},[{x,1},{atom,x}]}.
    {move,{x,2},{x,0}}.
    {jump,{f,12}}.
  {label,5}.
    {test,is_atom,{f,8},[{x,0}]}.
    %% 2分查找
    {select_val,{x,0},{f,21},{list,[{atom,true},{f,6},{atom,other},{f,7}]}}.
  {label,6}.
    {move,{integer,3},{x,0}}.
    {jump,{f,12}}.
  {label,7}.
    {move,{integer,2},{x,0}}.
    {jump,{f,12}}.
  {label,8}.
    {test,is_integer,{f,21},[{x,0}]}.
   %% 编译器会聪明的做这类事情
    {select_val,{x,0},
                {f,21},
                {list,[{integer,2},
                       {f,9},
                       {integer,3},
                       {f,10},
                       {integer,5},
                       {f,11}]}}.
  {label,9}.
    {move,{float,1.0},{x,0}}.
    {jump,{f,12}}.
  {label,10}.
    {move,{atom,a2},{x,0}}.
    {jump,{f,12}}.
  {label,11}.
    {move,{atom,a1},{x,0}}.
  {label,12}.
    {move,{x,0},{y,0}}.

%% receive语句
  {label,13}.
    {loop_rec,{f,19},{x,0}}.
    {test,is_tuple,{f,14},[{x,0}]}.
    {test,test_arity,{f,18},[{x,0},2]}.
    {get_tuple_element,{x,0},0,{x,1}}.
    {get_tuple_element,{x,0},1,{x,2}}.
    {test,is_eq_exact,{f,18},[{x,1},{atom,tag}]}.
   
    %%从消息队列移除
    remove_message.
    {move,{x,2},{x,0}}.
    {jump,{f,20}}.
  {label,14}.
    {test,is_atom,{f,17},[{x,0}]}.
    {select_val,{x,0},{f,18},{list,[{atom,a2},{f,15},{atom,a1},{f,16}]}}.
  {label,15}.
    remove_message.
    {move,{integer,5},{x,0}}.
    {jump,{f,20}}.
  {label,16}.
    remove_message.
    {move,{float,3.2},{x,0}}.
    {jump,{f,20}}.
  {label,17}.
    {test,is_eq_exact,{f,18},[{x,0},{integer,2}]}.
    remove_message.
    {move,{integer,3},{x,0}}.
    {jump,{f,20}}.
  {label,18}.
    remove_message.
    {move,{atom,ok},{x,0}}.
    {jump,{f,20}}.
  {label,19}.
    %% timeout添加到定时器
    {wait_timeout,{f,13},{integer,3}}.
    timeout.
    {move,{atom,timeout},{x,0}}.
  {label,20}.
    %% 闭包
    {move,{x,0},{x,1}}.
    {move,{y,0},{x,0}}.
    {move,{x,1},{y,0}}.
    {make_fun2,{f,39},0,133275192,2}.
    {move,{x,0},{x,1}}.
    {move,{y,0},{x,0}}.
    {trim,1,1}.
    {call_fun,1}.
    {move,{x,0},{x,1}}.
    {move,{y,0},{x,0}}.
    {move,{x,1},{y,0}}.
    {call,1,{f,23}}.
    {test_heap,4,1}.
    %% 列表操作
    {put_list,{x,0},{literal,[[1,2,3],[[4,1,2,3],9]]},{x,0}}.
    {put_list,{y,0},{x,0},{x,1}}.
    {trim,1,0}.
    {move,{literal,"~p~p~p~p~n"},{x,0}}.
    {call_ext,2,{extfunc,io,format,2}}.
    {move,{atom,done},{x,0}}.
    {deallocate,0}.
    return.
  {label,21}.
    {case_end,{x,0}}.


{function, f, 1, 23}.
  {label,22}.
    {func_info,{atom,gram},{atom,f},1}.
  {label,23}.
    {test,bs_start_match2,{f,24},1,[{x,0},0],{x,0}}.
    {test,bs_match_string,{f,33},[{x,0},8,{string,[1]}]}.
    {test,bs_get_integer2,
          {f,33},
          1,
          [{x,0},
           {integer,32},
           1,
           {field_flags,[{anno,[78,{file,"./gram.erl"}]},unsigned,big]}],
          {x,1}}.
    {test,bs_match_string,{f,33},[{x,0},24,{string,"xyz"}]}.
    {test,bs_get_float2,
          {f,33},
          2,
          [{x,0},
           {integer,64},
           1,
           {field_flags,[{anno,[78,{file,"./gram.erl"}]},unsigned,big]}],
          {x,2}}.
    {test,bs_test_tail2,{f,33},[{x,0},0]}.
    {test_heap,3,3}.
    {put_tuple,2,{x,0}}.
    {put,{x,1}}.
    {put,{x,2}}.
    return.
  {label,24}.
    {test,is_tuple,{f,25},[{x,0}]}.
    {test,test_arity,{f,33},[{x,0},2]}.
    {get_tuple_element,{x,0},0,{x,1}}.
    {get_tuple_element,{x,0},1,{x,2}}.
    {test,is_eq_exact,{f,33},[{x,1},{atom,x}]}.
    {test,is_integer,{f,33},[{x,2}]}.
    {select_val,{x,2},
                {f,33},
                {list,[{integer,5},
                       {f,26},
                       {integer,4},
                       {f,27},
                       {integer,3},
                       {f,28},
                       {integer,2},
                       {f,29},
                       {integer,1},
                       {f,30}]}}.
  {label,25}.
    {test,is_atom,{f,31},[{x,0}]}.
    {select_val,{x,0},
                {f,33},
                {list,[{atom,x5},
                       {f,26},
                       {atom,x4},
                       {f,27},
                       {atom,x3},
                       {f,28},
                       {atom,x2},
                       {f,29},
                       {atom,x1},
                       {f,30}]}}.
  {label,26}.
    {move,{integer,5},{x,0}}.
    return.
  {label,27}.
    {move,{integer,4},{x,0}}.
    return.
  {label,28}.
    {move,{integer,3},{x,0}}.
    return.
  {label,29}.
    {move,{integer,2},{x,0}}.
    return.
  {label,30}.
    {move,{integer,1},{x,0}}.
    return.
  {label,31}.
    {test,is_integer,{f,33},[{x,0}]}.
    {select_val,{x,0},
                {f,33},
                {list,[{integer,5},
                       {f,32},
                       {integer,4},
                       {f,32},
                       {integer,3},
                       {f,32},
                       {integer,2},
                       {f,32},
                       {integer,1},
                       {f,32}]}}.
  {label,32}.
    return.
  {label,33}.
    {move,{atom,err},{x,0}}.
    return.

%%这2个函数是complier要硬性加上去的

{function, module_info, 0, 35}.
  {label,34}.
    {func_info,{atom,gram},{atom,module_info},0}.
  {label,35}.
    {move,{atom,gram},{x,0}}.
    {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.


{function, module_info, 1, 37}.
  {label,36}.
    {func_info,{atom,gram},{atom,module_info},1}.
  {label,37}.
    {move,{x,0},{x,1}}.
    {move,{atom,gram},{x,0}}.
    {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

%%匿名函数的命名
{function, '-start/1-fun-0-', 3, 39}.
  {label,38}.
    {func_info,{atom,gram},{atom,'-start/1-fun-0-'},3}.
  {label,39}.
    {test,bs_start_match2,{f,40},3,[{x,0},0],{x,0}}.
    {test,bs_match_string,{f,44},[{x,0},16,{string,"12"}]}.
    {test,bs_test_tail2,{f,44},[{x,0},0]}.
    %% bitstring的代码很优化。
    {move,{integer,1},{x,0}}.
    return.
  {label,40}.
    {test,is_atom,{f,43},[{x,0}]}.
    {select_val,{x,0},{f,44},{list,[{atom,y},{f,41},{atom,x},{f,42}]}}.
   %% 一类的数据放在一起 用二分查找匹配
  {label,41}.
    {move,{x,1},{x,0}}.
    return.
  {label,42}.
    {move,{integer,2},{x,0}}.
    return.
  {label,43}.
    {test,is_eq_exact,{f,44},[{x,0},{integer,1}]}.
    {move,{x,2},{x,0}}.
    return.
  {label,44}.
    {move,{atom,error},{x,0}}.
    return.

所以无论函数match, 表达式match在vm层面都是if else这样的判断。从这个角度来讲if, case这些都只是erlang的语法糖。事实上也是,这些语法都是后来添加的,取悦用户的。

函数匹配是erlang的所有事情的核心。

结论:erlang的compiler很智能,这个VM和lua的非常像, 效率也相当。

Categories: Erlang探索 Tags: , ,