我正在使用一种可转换为JavaScript的语言。为了避免一些堆栈溢出,我通过将某些函数转换为for循环来应用尾调用优化。令人惊讶的是,转换不比递归版本更快。
http://jsperf.com/sldjf-lajf-lkajf-lkfadsj-f/5
递归版:
(function recur(a0,s0){
return a0==0 ? s0 : recur(a0-1, a0+s0)
})(10000,0)
尾调用优化后:
ret3 = void 0;
a1 = 10000;
s2 = 0;
(function(){
while (!ret3) {
a1 == 0
? ret3 = s2
: (a1_tmp$ = a1 - 1 ,
s2_tmp$ = a1 + s2,
a1 = a1_tmp$,
s2 = s2_tmp$);
}
})();
ret3;
使用Google Closure Compiler进行一些清理后:
ret3 = 0;
a1 = 1E4;
for(s2 = 0; ret3 == 0;)
0 == a1
? ret3 = s2
: (a1_tmp$ = a1 - 1 ,
s2_tmp$ = a1 + s2,
a1 = a1_tmp$,
s2 = s2_tmp$);
c=ret3;
递归版本比“优化”版本更快!如果递归版本必须处理数以千计的上下文更改,那怎么可能呢?
答案 0 :(得分:5)
优化比尾部调用优化还要多。
例如,我注意到你正在使用两个临时变量,只需要:
s2 += a1;
a1--;
仅这一点实际上将操作次数减少了三分之一,导致性能提高了50%
从长远来看,在尝试优化操作之前优化正在执行的操作非常重要。
编辑:这是更新后的jsperf
答案 1 :(得分:2)
as Kolink 说出你的代码片段只是将n
添加到总数中,将n
减少1,然后循环直到n
无法覆盖0
所以就这样做:
n = 10000, o = 0; while(n) o += n--;
它是 more faster并且比递归版本可见,并且当然输出same result
答案 2 :(得分:2)
递归版本中没有太多的上下文更改,因为命名函数recur
包含在recur
本身的范围内/它们共享相同的范围。其原因与JavaScript引擎评估范围的方式有关,并且有很多网站可以解释这个主题,所以我不会在这里做。再看一下,你会注意到recur
也是一个所谓的“纯”函数,这基本上意味着只要内部执行运行它就永远不必离开它自己的范围(简单地说:直到它返回一个值) )。这两个事实使它基本上很快。我只想在这里提一下,第一个例子是唯一的尾部调用优化了三个中的一个 - tc优化只能在递归函数中完成,这是唯一的递归函数。
然而,第二个看第二个例子(没有双关语)揭示了“优化器”让你变得更糟,因为它通过将操作分成
将范围引入前一个纯函数while
循环这导致性能较差,因为现在引擎必须处理10000个上下文更改。
说实话我不知道为什么第三个例子的性能比递归的差,所以可能与它有关: