首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java迭代与递归

Java迭代与递归
EN

Stack Overflow用户
提问于 2012-09-05 11:28:53
回答 1查看 1.4K关注 0票数 3

谁能解释一下为什么下面的递归方法比迭代方法更快(两者都在做字符串连接)?迭代方法不是应该比递归方法更好吗?另外,每次递归调用都会在堆栈的顶部添加一个新的层,这可能是非常低效的空间。

代码语言:javascript
复制
    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倍。导致迭代速度变慢的主要原因是什么?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-09-05 11:34:49

参见my comments

确保您学习如何正确地进行微基准测试。你应该对两者的多次迭代进行计时,并在你的时间内平均这些迭代。除此之外,您应该确保VM不会因为不编译第一个而给第二个带来不公平的优势。

实际上,默认的HotSpot编译阈值(可通过-XX:CompileThreshold配置)是10,000次调用,这可能解释了您在这里看到的结果。HotSpot实际上不做任何尾部优化,所以递归解决方案更快是很奇怪的。将StringBuilder.append编译成本机代码主要是为了实现递归解决方案,这是很有可能的。

我决定重写基准测试,并亲自查看结果。

代码语言:javascript
复制
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递归: 405.41us迭代: 313.20us C:\dev\scrap>java -server AppendMicrobenchmark递归: 397.43us迭代: 312.14us

这是每种方法平均超过1000次试验的时间。

从本质上讲,您的基准测试的问题是,它不是许多试验(law of large numbers)的平均值,并且它高度依赖于单个基准测试的顺序。我给你的原始结果是:

C:\dev\scrap>java StringBuilderBenchmark 80 41

这对我来说意义不大。HotSpot VM上的递归很可能不会像迭代那样快,因为到目前为止,它还没有实现您可能会发现用于函数式语言的任何类型的尾部优化。

现在,有趣的是,默认的HotSpot即时编译阈值是10,000次调用。在编译append之前,您的迭代基准测试很可能大部分时间都在执行。另一方面,您的递归方法应该相对较快,因为它很可能在编译后使用append。为了消除这一点对结果的影响,我传递了-XX:CompileThreshold=0并发现...

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

因此,当涉及到它时,它们的速度大致相等。然而,请注意,如果您以更高的精度进行平均,迭代似乎会更快一些。顺序可能仍然会影响我的基准测试,因为后一个基准测试的优势是VM收集了更多的统计信息来进行动态优化。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12274120

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档