2014-05-16 53 views
3

要迭代一個迭代器,我們可以調用它的foreach或使用while循環。的foreach實現是:scala迭代器#foreach性能問題

def foreach[U](f: A => U) { while (hasNext) f(next()) } 

所以我覺得應該foreachwhile(iterator.hasNext),但做了一些測試後,結果讓我感到吃驚非常。

我的測試代碼:

def getSize2[T](i: Iterator[T]) = { 
    var count = 0 
    val f = (a: T) => count += 1 
    while(i.hasNext) { 
    f(i.next) 
    } 
    count 
} 

def getSize3[T](i: Iterator[T]) = { 
    var count = 0 
    val f = (a: T) => count += 1 
    i.foreach(f) 
    count 
} 

這是非常奇怪的是getSize2getSize3快3倍!

任何人都知道那裏發生了什麼?

編輯: 貼我的測試程序

def main(args: Array[String]) { 
    val data = 0 to 100000000 

    val start2 = System.nanoTime 
    (0 to 100).foreach(_ => getSize2(data.iterator)) 
    println("get size, while loop, using function: " + (System.nanoTime - start2)/1000000) 

    val start3 = System.nanoTime 
    (0 to 100).foreach(_ => getSize3(data.iterator)) 
    println("get size, foreach: " + (System.nanoTime - start3)/1000000) 

} 

我的操作系統:Ubuntu的12.04,斯卡拉版本:2.10.3

+1

你可以寫你如何衡量執行速度?我的意思是你執行的實際代碼和命令行。另外scala版本和你正在運行的操作系統。 – ymonad

+1

看看字節碼,你會看到foreach正在做更多的調用。 – monkjack

+1

這很有趣,因爲我看到了相反的結果。 while(iter.hasNext){count + = 1; iter.next()}需要6倍的時間。 – Snnappie

回答

4

while循環更快,因爲函數調用是不是免費的,不能總是由JIT編譯器刪除。特別是,var count被封裝在一個匿名對象中,因此它可以從函數對象中訪問它,並且真正加速JIT編譯器解封所有內容,然後最終意識到它根本不需要匿名對象。

將一個函數調用的額外層添加到庫中foreach確實使JIT編譯器的分析變得複雜(三層間接而不是兩層等)。

+0

我還在'while循環版本的匿名函數對象中包裝'var count'。你的意思是JIT更有可能在while循環版本而不是foreach版本中刪除函數調用? – cloud

+0

@cloud - 是的,這就是我的意思。有很少的工作要做,這往往會有所作爲。 (編輯答案更清楚。) –