简体   繁体   English

Java的奇怪递归优化

[英]Strange recursion optimization by java

I came across some weird results when I was trying to answer this question: How to improve the performance of the recursive method? 当我试图回答这个问题时,我遇到了一些奇怪的结果: 如何提高递归方法的性能?

But you don't need to read that post. 但是你不需要阅读那篇文章。 I'll give the relevant context here. 我将在这里给出相关背景。 This might seem lengthy but really isn't that complicated if you read through once. 这可能看起来很冗长,但如果你通读一次,那真的不是那么复杂。 I hope it will be interesting for all. 我希望所有人都感兴趣。 For context, 对于上下文,

syra(n) = { 1 if n=1; 
            n + syra(n/2) if n is even; and 
            n + syra(3n+1) if n is odd
          }

and

syralen(n) = No. of steps to calculate syra (n)

eg, syralen(1)=1 , syralen(2)=2 since we need to go two steps. 例如, syralen(1)=1syralen(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) = 10 + syra(5) = 15 + syra(16) = 31 + syra(8) = 39 + syra(4) = 43 + syra(2) = 45 + syra(1) = 46 So syra(10) needed 7 steps. 所以syra(10)需要7个步骤。 Therefore syralen(10)=7 因此, syralen(10)=7

And finally, 最后,

lengths(n) = syralen(1)+syralen(2)+...+syralen(n)

The question poster there is trying to calculate lengths(n) 那里的问题海报试图计算lengths(n)

My question is about the recursive solution the Op has posted (which is the second snippet in that question). 我的问题是关于Op发布的递归解决方案(这是该问题的第二个片段)。 I'll repost it here: 我会在这里重新发布:

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));
        }
       }

Surely an unusual (and probably not recommended) way of doing it recursively, but it does calculate the right thing, I have verified that. 肯定是一种不寻常的(也可能不是推荐的)递归方式,但它确实计算了正确的事情,我已经验证了这一点。 I tried to write a more usual recursive version of the same: 我试着写一个更常见的递归版本:

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));
    }

Now here's the weird part - I tried to test the performance of both the above versions like: 现在这里是奇怪的部分 - 我试图测试上述两个版本的性能,如:

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));
        }

For TEST_VAL=50000 , the output is: 对于TEST_VAL=50000 ,输出为:

5075114
SyraLengths time taken: 44
5075114
SyraSlow time taken: 31

As expected (I guess) the plain recursion is slightly better. 正如预期的那样(我猜),普通的递归稍好一些。 But when I go one step further and use TEST_VAL=500000 , the output is: 但是当我更进一步并使用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)

Why is it? 为什么? What kind of optimization is being done by Java here that the SyraLengths version doesn't hit StackOverflow (It works even at TEST_VAL=5000000 )? 在这里,Java正在进行什么样的优化,SyraLengths版本没有达到StackOverflow(它甚至在TEST_VAL=5000000 )? I even tried using an accumulator-based recursive version just in case my JVM was doing some tail-call optimization: 我甚至尝试使用基于累加器的递归版本,以防万一我的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);
    }

But I still got the same result (thus there is no tail-call optimization here). 但我仍然得到相同的结果(因此这里没有尾调用优化)。 So then, what's happening here? 那么,这里发生了什么?

PS: Please edit to a better title if you can think of any. PS:如果你能想到,请编辑一个更好的标题。

With the original version, tail recursion optimization was possible (within the JIT). 使用原始版本,尾部递归优化是可能的(在JIT内)。 Dunno whether it actually occurred or not, though. 但是Dunno是否真的发生了。 But it's possible that the original is just slightly more efficient with heap [er, I mean stack] use. 但是有可能原来只是稍微有效一点堆[呃,我的意思是堆栈]使用。 (Or there may be a functional difference that was not obvious on cursory examination.) (或者在粗略检查中可能存在功能差异并不明显。)

Well, it turns out there is a simple explanation to it: 嗯,事实证明它有一个简单的解释:

I am using long syraLen(int n) as the method signature. 我使用long syraLen(int n)作为方法签名。 But the value of n can actually be much larger than Integer.MAX_VALUE . 但是n的值实际上可能远大于Integer.MAX_VALUE So syraLen gets negative inputs and therein lies the problem. 因此, syraLen得到负面投入,其中存在问题。 If I change it to long syraLen(long n) , everything works perfectly! 如果我把它改long syraLen(long n) ,一切都很完美! I wish I had also put the if(n < 1) throw new IllegalArgumentException(); 我希望我也把if(n < 1) throw new IllegalArgumentException(); like the original poster. 像原始的海报。 Would have saved me some time. 本来可以节省我一些时间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM