我想知道为什么"尾部调用"模式在Java中表现得更好,然后使用经典的递归模式。
给出以下示例:
public static long facultyApproachPureRec(long n)
{
if(n == 0 || n == 1)
{
return 1;
}
long temp = facultyApproachPureRec(n-1);
return (n*temp);
}
public static long facultyApproachTailEnd(long n, long m)
{
if(n == 0 || n == 1)
{
return m;
}
long temp = n*m;
return facultyApproachTailEnd(n-1, temp);
}
facultyApproachTailEnd总是比纯递归解决方案更快。这让我想知道为什么因为Java不支持尾调用优化(我证明了这一点:我可以轻松获得StachOverflowError)。 因为这个事实我会假设尾调用解决方案比纯递归求解更差,因为我们总是需要传递双倍量的变量。
有人可以解释一下为什么基准测试表明尾部调用总是比其他人快吗?
注意:在c#中,它的行为就像我预期的那样:尾调用解决方案总是比纯递归解决方案慢。
基准代码:
public static void main(String[] args)
{
for(int k = 0; k < 10; k++) {
long begin = System.nanoTime();
for (int i = 0; i < 20000; i++) {
if (facultyApproachPureRec(i) == 0)
{
i++;
}
}
long end = System.nanoTime();
long elapsed = TimeUnit.MILLISECONDS.convert((end - begin), TimeUnit.NANOSECONDS);
System.out.println("Pure recursion took " + elapsed);
begin = System.nanoTime();
for (int i = 0; i < 20000; i++) {
if(facultyApproachTailEnd(i, 1) == 0)
{
i++;
}
}
end = System.nanoTime();
elapsed = TimeUnit.MILLISECONDS.convert((end - begin), TimeUnit.NANOSECONDS);
System.out.println("Tail end took " + elapsed);
}
}
一些结果(在3台机器上验证,都或多或少相同):
Pure recursion took 623
Tail end took 461
Pure recursion took 570
Tail end took 418
Pure recursion took 913
Tail end took 581
Pure recursion took 580
Tail end took 417
Pure recursion took 607
Tail end took 411
Pure recursion took 601
Tail end took 417
Pure recursion took 769
Tail end took 372
Pure recursion took 527
Tail end took 568
Pure recursion took 542
Tail end took 419
Pure recursion took 632
Tail end took 333
感谢所有答案。
编辑:我在写这个问题之前经常测试这个(1000次迭代,直接通过cmd(所以没有IDE))。如果我的测试错了我很抱歉。我在大学以外没有使用过Java。
Edit2:我在下面添加了生成的ByteCode。我不熟悉Java字节代码(我更习惯于clr),但从简短的角度来看,它们看起来非常相同,只是尾部调用解决方案内部有更多代码并使用/维护更多变量。
public static facultyApproachPureRec(J)J
L0
LINENUMBER 39 L0
LLOAD 0
LCONST_0
LCMP
IFEQ L1
LLOAD 0
LCONST_1
LCMP
IFNE L2
L1
LINENUMBER 41 L1
FRAME SAME
LCONST_1
LRETURN
L2
LINENUMBER 44 L2
FRAME SAME
LLOAD 0
LCONST_1
LSUB
INVOKESTATIC Logik/Rekursion/MainLogik.facultyApproachPureRec (J)J
LSTORE 2
L3
LINENUMBER 45 L3
LLOAD 0
LLOAD 2
LMUL
LRETURN
L4
LOCALVARIABLE n J L0 L4 0
LOCALVARIABLE temp J L3 L4 2
MAXSTACK = 4
MAXLOCALS = 4
// access flags 0x9
public static facultyApproachTailEnd(JJ)J
L0
LINENUMBER 50 L0
LLOAD 0
LCONST_0
LCMP
IFEQ L1
LLOAD 0
LCONST_1
LCMP
IFNE L2
L1
LINENUMBER 52 L1
FRAME SAME
LLOAD 2
LRETURN
L2
LINENUMBER 55 L2
FRAME SAME
LLOAD 0
LLOAD 2
LMUL
LSTORE 4
L3
LINENUMBER 56 L3
LLOAD 0
LCONST_1
LSUB
LLOAD 4
INVOKESTATIC Logik/Rekursion/MainLogik.facultyApproachTailEnd (JJ)J
LRETURN
L4
LOCALVARIABLE n J L0 L4 0
LOCALVARIABLE m J L0 L4 2
LOCALVARIABLE temp J L3 L4 4
MAXSTACK = 4
MAXLOCALS = 6
}