[英]Strange recursion optimization by java
当我试图回答这个问题时,我遇到了一些奇怪的结果: 如何提高递归方法的性能?
但是你不需要阅读那篇文章。 我将在这里给出相关背景。 这可能看起来很冗长,但如果你通读一次,那真的不是那么复杂。 我希望所有人都感兴趣。 对于上下文,
syra(n) = { 1 if n=1;
n + syra(n/2) if n is even; and
n + syra(3n+1) if n is odd
}
和
syralen(n) = No. of steps to calculate syra (n)
例如, syralen(1)=1
, syralen(2)=2 since we need to go two steps.
syra(10) = 10 + syra(5) = 15 + syra(16) = 31 + syra(8) = 39 + syra(4) = 43 + syra(2) = 45 + syra(1) = 46
。 所以syra(10)需要7个步骤。 因此, syralen(10)=7
最后,
lengths(n) = syralen(1)+syralen(2)+...+syralen(n)
那里的问题海报试图计算lengths(n)
我的问题是关于Op发布的递归解决方案(这是该问题的第二个片段)。 我会在这里重新发布:
public class SyraLengths{
int total=1;
public int syraLength(long n) {
if (n < 1)
throw new IllegalArgumentException();
if (n == 1) {
int temp=total;
total=1;
return temp;
}
else if (n % 2 == 0) {
total++;
return syraLength(n / 2);
}
else {
total++;
return syraLength(n * 3 + 1);
}
}
public int lengths(int n){
if(n<1){
throw new IllegalArgumentException();
}
int total=0;
for(int i=1;i<=n;i++){
total+=syraLength(i);
}
return total;
}
public static void main(String[] args){
System.out.println(new SyraLengths().lengths(5000000));
}
}
肯定是一种不寻常的(也可能不是推荐的)递归方式,但它确实计算了正确的事情,我已经验证了这一点。 我试着写一个更常见的递归版本:
public class SyraSlow {
public long lengths(int n) {
long total = 0;
for (int i = 1; i <= n; ++i) {
total += syraLen(i);
}
return total;
}
private long syraLen(int i) {
if (i == 1)
return 1;
return 1 + ((i % 2 == 0) ? syraLen(i / 2) : syraLen(i * 3 + 1));
}
现在这里是奇怪的部分 - 我试图测试上述两个版本的性能,如:
public static void main(String[] args){
long t1=0,t2=0;
int TEST_VAL=50000;
t1 = System.currentTimeMillis();
System.out.println(new SyraLengths().lengths(TEST_VAL));
t2 = System.currentTimeMillis();
System.out.println("SyraLengths time taken: " + (t2-t1));
t1 = System.currentTimeMillis();
System.out.println(new SyraSlow().lengths(TEST_VAL));
t2 = System.currentTimeMillis();
System.out.println("SyraSlow time taken: " + (t2-t1));
}
对于TEST_VAL=50000
,输出为:
5075114
SyraLengths time taken: 44
5075114
SyraSlow time taken: 31
正如预期的那样(我猜),普通的递归稍好一些。 但是当我更进一步并使用TEST_VAL=500000
,输出是:
62634795
SyraLengths time taken: 378
Exception in thread "main" java.lang.StackOverflowError
at SyraSlow.syraLen(SyraSlow.java:15)
at SyraSlow.syraLen(SyraSlow.java:15)
at SyraSlow.syraLen(SyraSlow.java:15)
为什么? 在这里,Java正在进行什么样的优化,SyraLengths版本没有达到StackOverflow(它甚至在TEST_VAL=5000000
)? 我甚至尝试使用基于累加器的递归版本,以防万一我的JVM正在做一些尾调用优化:
private long syraLenAcc(int i, long acc) {
if (i == 1) return acc;
if(i%2==0) {
return syraLenAcc(i/2,acc+1);
}
return syraLenAcc(i * 3 + 1, acc+1);
}
但我仍然得到相同的结果(因此这里没有尾调用优化)。 那么,这里发生了什么?
PS:如果你能想到,请编辑一个更好的标题。
使用原始版本,尾部递归优化是可能的(在JIT内)。 但是Dunno是否真的发生了。 但是有可能原来只是稍微有效一点堆[呃,我的意思是堆栈]使用。 (或者在粗略检查中可能存在功能差异并不明显。)
嗯,事实证明它有一个简单的解释:
我使用long syraLen(int n)
作为方法签名。 但是n
的值实际上可能远大于Integer.MAX_VALUE
。 因此, syraLen
得到负面投入,其中存在问题。 如果我把它改long syraLen(long n)
,一切都很完美! 我希望我也把if(n < 1) throw new IllegalArgumentException();
像原始的海报。 本来可以节省我一些时间。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.