您的位置:首页 > 大数据 > 人工智能

Erlang tail recursion和body recursion 在OTP19.1版本上执行时间差异

2016-12-14 06:26 465 查看
根据Erlang Doc, 从R7B版本之后, 尾递归在执行时间上和body递归相比就没有什么差异了, 具体选择哪种方式完全看个人偏好.

那在OTP19.1里面是不是在执行时间上一点差异都没有呢?

我们来写个例子测试一下, 测试代码如下

-module(test_tail).

-compile(export_all).

tail_fac(N) -> tail_fac(N,1).

 

tail_fac(0,Acc) -> Acc;

tail_fac(N,Acc) when N > 0 -> tail_fac(N-1,N+Acc).

fac(0)->1;

fac(N)->fac(N-1)+N.

当递归次数较小时,  执行时间上几乎没有差异,只有当递归次数到达一定量级以后,时间差异才可能体现出来.

为什么说可能呢,因为我测试了多次,总体来说尾递归时间上占优,但是有几次运行测试的时候,发现即使量级很大的时候,时间差异也不是很大,几乎在一个等级上。

下面的一次测试输出,是到了比较大量级的时候时间差异比较大的一次.

所以,  至少从这个例子说明, tail recursion 在运行时间上没有什么优势.

47> timer:tc(test_tc, start, [1]).      

{3,2}

48> timer:tc(test_tc, fac, [1]).   

{4,2}

49> timer:tc(test_tc, start, [10]).

{2,56}

50> timer:tc(test_tc, fac, [10]).  

{3,56}

51> timer:tc(test_tc, start, [100]).

{6,5051}

52> timer:tc(test_tc, fac, [100]).  

{7,5051}

53> timer:tc(test_tc, start, [1000]).

{35,500501}

54> timer:tc(test_tc, fac, [1000]).  

{47,500501}

55> timer:tc(test_tc, start, [10000]).

{357,50005001}

56> timer:tc(test_tail, fac, [10000]).  

{457,50005001}

57> timer:tc(test_tail, tail_fac, [100000]).

{3395,5000050001}

58> timer:tc(test_tail, fac, [100000]).  

{4417,5000050001}

59> timer:tc(test_tail, tail_fac, [1000000]).                          %%从这里开始体现出执行时间上的差别, 越往后随着数字的增大,执行时间上的差距也越来越大

{33932,500000500001}

60> timer:tc(test_tail, fac, [1000000]).  

{55181,500000500001}

61> timer:tc(test_tail, tail_fac, [10000000]).

{343071,50000005000001}

62> timer:tc(test_tail, fac, [10000000]).  

{1321742,50000005000001}

63> timer:tc(test_tail, tail_fac, [20000000]).

{681692,200000010000001}

64> timer:tc(test_tail, fac, [20000000]).  

{1968632,200000010000001}                                            %%从这里差不多达到三倍.

65> timer:tc(test_tail, tail_fac, [2000000000]).

{81661718,2000000001000000001}                                %%tail_fac运行了差不多81.6秒

66> 

66> timer:tc(test_tc, fac, [2000000000]).                            %%fac直接crash了, 因为没有可用的空间用来压栈了.

eheap_alloc: Cannot allocate 9794840320 bytes of memory (of type "heap").

Crash dump is being written to: erl_crash.dump...done

#官方文档关于不能递归方式性能方面差异的说明

2.3  Myth: Tail-Recursive Functions are Much Faster Than Recursive Functions

According to the myth, recursive functions leave references to dead terms on the stack and the garbage collector has to copy all those dead
terms, while tail-recursive functions immediately discard those terms.
That used to be true before R7B. In R7B, the compiler started to generate code that overwrites references to terms that will never be used with an empty list, so that the garbage collector
would not keep dead values any longer than necessary.
Even after that optimization, a tail-recursive function is still most of the times faster than a body-recursive function. Why?
It has to do with how many words of stack that are used in each recursive call. In most cases, a recursive function uses more words on the stack for each recursion than the number of
words a tail-recursive would allocate on the heap. As more memory is used, the garbage collector is invoked more frequently, and it has more work traversing the stack.
In R12B and later releases, there is an optimization that in many cases reduces the number of words used on the stack in body-recursive calls. A body-recursive list function and a tail-recursive
function that calls lists:reverse/1 at the end will
use the same amount of memory. lists:map/2, lists:filter/2, list comprehensions, and many other recursive functions now use the
same amount of space as their tail-recursive equivalents.
So, which is faster? It depends. On Solaris/Sparc, the body-recursive function seems to be slightly faster, even for lists with a lot of elements. On the x86 architecture, tail-recursion
was up to about 30% faster.
So, the choice is now mostly a matter of taste. If you really do need the utmost speed, you must measure. You can no longer be sure that the tail-recursive list function
always is the fastest.

Note

A tail-recursive function that does not need to reverse the list at the end is faster than a body-recursive function, as are tail-recursive functions that do not construct any terms at all (for example, a function that sums all integers in a list).

##博客仅作个人记录##
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: