簡體   English   中英

Java的奇怪遞歸優化

[英]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)=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)需要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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM