谁能解释一下为什么下面的递归方法比迭代方法更快(两者都在做字符串连接)?迭代方法不是应该比递归方法更好吗?另外,每次递归调用都会在堆栈的顶部添加一个新的层,这可能是非常低效的空间。
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倍。导致迭代速度变慢的主要原因是什么?
发布于 2012-09-05 11:34:49
参见my comments。
确保您学习如何正确地进行微基准测试。你应该对两者的多次迭代进行计时,并在你的时间内平均这些迭代。除此之外,您应该确保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递归: 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收集了更多的统计信息来进行动态优化。
https://stackoverflow.com/questions/12274120
复制相似问题