据小道可靠消息,在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亿次或更多时就有可能会出现内存溢出。
而尾递归,函数调用会直接重用当前函数的调用堆栈,不会额外增加新的嵌套调用堆栈,所以编译器就帮你优化成循环了。