簡體   English   中英

Java迭代與遞歸

[英]Java iterative vs recursive

任何人都可以解釋為什么下面的遞歸方法比迭代方法更快(兩者都在進行字符串連接)? 是不是迭代的方法想要打敗遞歸的? 加上每個遞歸調用在堆棧頂部添加一個新層,這可能是非常低效的空間。

    private static void string_concat(StringBuilder sb, int count){
        if(count >= 9999) return;
        string_concat(sb.append(count), count+1);
    }
    public static void main(String [] arg){

        long s = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 9999; i++){
            sb.append(i);
        }
        System.out.println(System.currentTimeMillis()-s);
        s = System.currentTimeMillis();
        string_concat(new StringBuilder(),0);
        System.out.println(System.currentTimeMillis()-s);

    }

我多次運行該程序,遞歸的程序總是比迭代程序快3-4倍。 可能導致迭代速度變慢的主要原因是什么?

我的評論

確保您學習如何正確地進行微基准測試。 您應該計算兩次迭代的時間並對這些時間進行平均。 除此之外,你應該確保VM沒有通過不編譯第一個來給第二個不公平的優勢。

實際上,默認的HotSpot編譯閾值(可通過-XX:CompileThreshold配置)是10,000次調用,這可能會解釋您在此處看到的結果。 HotSpot並沒有真正進行任何尾部優化,因此遞歸解決方案更快是很奇怪的。 StringBuilder.append編譯為本機代碼主要用於遞歸解決方案是非常合理的。

我決定改寫基准測試並親自查看結果。

public final class AppendMicrobenchmark {

  static void recursive(final StringBuilder builder, final int n) {
    if (n > 0) {
      recursive(builder.append(n), n - 1);
    }
  }

  static void iterative(final StringBuilder builder) {
    for (int i = 10000; i >= 0; --i) {
      builder.append(i);
    }
  }

  public static void main(final String[] argv) {
    /* warm-up */
    for (int i = 200000; i >= 0; --i) {
      new StringBuilder().append(i);
    }

    /* recursive benchmark */
    long start = System.nanoTime();
    for (int i = 1000; i >= 0; --i) {
      recursive(new StringBuilder(), 10000);
    }
    System.out.printf("recursive: %.2fus\n", (System.nanoTime() - start) / 1000000D);

    /* iterative benchmark */
    start = System.nanoTime();
    for (int i = 1000; i >= 0; --i) {
      iterative(new StringBuilder());
    }
    System.out.printf("iterative: %.2fus\n", (System.nanoTime() - start) / 1000000D);
  }
}

這是我的結果......

 C:\\dev\\scrap>java AppendMicrobenchmark recursive: 405.41us iterative: 313.20us C:\\dev\\scrap>java -server AppendMicrobenchmark recursive: 397.43us iterative: 312.14us 

這些是平均超過1000次試驗的每種方法的時間。

從本質上講,您的基准測試的問題在於它不會對許多試驗( 大數定律)進行平均,並且它高度依賴於各個基准的排序。 我給你的最初結果是:

 C:\\dev\\scrap>java StringBuilderBenchmark 80 41 

這對我來說沒什么意義。 HotSpot VM上的遞歸很可能不會像迭代那樣快,因為它還沒有實現您可能用於函數式語言的任何類型的尾部優化。

現在,這里發生的有趣的事情是默認的HotSpot JIT編譯閾值是10,000次調用。 編譯append 之前,您的迭代基准測試很可能會在大多數情況下執行。 另一方面,您的遞歸方法應該相對較快,因為它很可能在編譯享受append 為了消除這影響結果,我通過-XX:CompileThreshold=0並找到...

 C:\\dev\\scrap>java -XX:CompileThreshold=0 StringBuilderBenchmark 8 8 

所以,當它歸結為它時,它們的速度大致相等。 但請注意,如果平均值具有更高的精度,則迭代似乎會更快一些。 訂單可能仍會對我的基准測試產生影響,因為后者的基准測試將具有VM為其動態優化收集更多統計信息的優勢。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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