Erlang递归和尾递归

    据小道可靠消息,在Erlang中是没有循环的,要使用循环可以用递归来代替!先上代码:

-module(recursion2).

-compile(export_all).

sum(1) ->
    1;
sum(N) ->
    N + sum(N - 1).

 

    这就是递归,函数自己调用自己。但这就是传说中可以通过编译优化得“和循环一样快,没有额外开销”的尾递归么?

    我们来做一个实验:

-module(recursion2).

-compile(export_all).

t1(N) ->
    Result = sum(N),
    io:format("~p~n", [Result]),
    erlang:process_info(self()).

sum(1) ->
    1;
sum(N) ->
    N + sum(N - 1).

    我们来运行一下:

Eshell V5.10.1  (abort with ^G)
1> recursion2:t1(10000000).
50000005000000
[{current_function,{recursion2,t1,1}},
 {initial_call,{erlang,apply,2}},
 {status,running},
 {message_queue_len,0},
 {messages,[]},
 {links,[<0.27.0>]},
 {dictionary,[]},
 {trap_exit,false},
 {error_handler,error_handler},
 {priority,normal},
 {group_leader,<0.26.0>},
 {total_heap_size,22177879},
 {heap_size,22177879},
 {stack_size,25},
 {reductions,10001666},
 {garbage_collection,[{min_bin_vheap_size,46422},
                      {min_heap_size,233},
                      {fullsweep_after,65535},
                      {minor_gcs,0}]},
 {suspending,[]}]
2> 

    其中”{heap_size,22177879},” 表示堆区内存占用为 22177879 words(32位系统1word为4byte,64位系统1word为8byte, 可以通过erlang:system_info(wordsize) .查看),在64位系统下169.2MB(22177879  * 8 / 1024 / 1024), 太夸张了!

    Erlang不会真的这么弱吧?只一个简单的从1+2+3+...+10000000就吃掉169M内存,如果1亿或更多时内存不是会被吃完!

    其实并不是你想的那样,没错,这是递归,但不是尾递归。我们来看这个算法的另一个版本:

-module(recursion2).

-compile(export_all).

t2(N) ->
    Result = tail_sum(N),
    io:format("~p~n", [Result]),
    erlang:process_info(self()).

tail_sum(N) ->
    tail_sum(N, 1).
tail_sum(1, Result) ->
    Result;
tail_sum(N, Result) ->
    tail_sum(N - 1, N + Result).

   再运行一下:

Eshell V5.10.1  (abort with ^G)
1> recursion2:t2(10000000).
50000005000000
[{current_function,{recursion2,t2,1}},
 {initial_call,{erlang,apply,2}},
 {status,running},
 {message_queue_len,0},
 {messages,[]},
 {links,[<0.27.0>]},
 {dictionary,[]},
 {trap_exit,false},
 {error_handler,error_handler},
 {priority,normal},
 {group_leader,<0.26.0>},
 {total_heap_size,2585},
 {heap_size,1598},
 {stack_size,25},
 {reductions,10001574},
 {garbage_collection,[{min_bin_vheap_size,46422},
                      {min_heap_size,233},
                      {fullsweep_after,65535},
                      {minor_gcs,4}]},
 {suspending,[]}]

    看到木有,奇迹出现了,堆区内存仅仅占用0.0121MB({heap_size,1598},)!!

    普通的递归,每一层函数调用结果都会保存在堆栈中,当循环次数多时,比如像上面的1亿次或更多时就有可能会出现内存溢出。

    而尾递归,函数调用会直接重用当前函数的调用堆栈,不会额外增加新的嵌套调用堆栈,所以编译器就帮你优化成循环了。

posted @ 2014-02-26 14:57  hongmao  阅读(1793)  评论(1编辑  收藏  举报