[英]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.