2013-05-28 77 views
6

有人說Scala對於理解其實很慢。我得到的理由是,由於Java的限制,對於理解(比如「reduce」,下面使用)需要生成一個臨時對象,每次迭代以調用傳入的函數。與FOR循環相比,爲什麼Scala「for循環理解」非常慢?

IS .. 。這是真的?下面的測試似乎證實了這一點,但我不完全明白爲什麼會出現這種情況。

這可能對「lambdas」或匿名函數有意義,但不適用於非匿名函數。

在我的測試中,我運行了針對list.reduce的循環(見下面的代碼),並發現它們的速度是以前的兩倍,即使每次迭代都調用完全相同的函數以減少!

我覺得這非常不直觀(一旦認爲Scala庫會被精心地創建爲儘可能優化)。

在測試I放在一起,我跑的相同環路(總結編號1〜一百萬,而不論溢出的)五種不同的方法:

  1. for循環值的陣列之上
  2. for循環,而是調用一個函數,而不是在線算術
  3. for循環,創建一個包含除了功能
  4. list.reduce一個對象,我傳遞一個匿名函數
  5. list.reduce,傳遞對象的成員函數

結果如下: 測試:最小/最大/平均值(毫秒)

1. 27/157/64.78 
2. 27/192/65.77 <--- note the similarity between tests 1,2 and 4,5 
3. 139/313/202.58 
4. 63/342/150.18 
5. 63/341/149.99 

如可以看到的,在「用於理解」版本的「量級對於每個實例都是新的「,這意味着實際上可以爲匿名和非匿名函數版本執行」新「。

方法:將下面的代碼(已刪除測試調用)編譯爲單個.jar文件,以確保所有版本都運行相同的庫代碼。每次迭代中的每個測試都在新的JVM中調用(即每次測試的scala -cp ...)以消除堆大小問題。

class t(val i: Int) { 
    def summit(j: Int) = j + i 
} 

object bar { 
    val biglist:List[Int] = (1 to 1000000).toList 

    def summit(i: Int, j:Int) = i+j 

    // Simple for loop 
    def forloop: Int = { 
     var result: Int = 0 
     for(i <- biglist) { 
      result += i 
     } 
     result 
    } 

    // For loop with a function instead of inline math 
    def forloop2: Int = { 
     var result: Int = 0 
     for(i <- biglist) { 
      result = summit(result,i) 
     } 
     result 
    } 

    // for loop with a generated object PER iteration 
    def forloop3: Int = { 
     var result: Int = 0 
     for(i <- biglist) { 
      val t = new t(result) 
      result = t.summit(i) 
     } 
     result 
    } 

    // list.reduce with an anonymous function passed in 
    def anonymousfunc: Int = { 
     biglist.reduce((i,j) => {i+j}) 
    } 

    // list.reduce with a named function 
    def realfunc: Int = { 
     biglist.reduce(summit) 
    } 

    // test calling code excised for brevity. One example given: 
    args(0) match { 
     case "1" => { 
        val start = System.currentTimeMillis() 
        forloop 
        val end = System.currentTimeMillis() 
        println("for="+(end - start)) 
        } 
     ... 
} 
+1

'.reduce'與「for comprehensions」無關 –

+0

作爲一個方面說明,scala編譯器插件旨在消除常見情況下的這種開銷:https://code.google.com/p/scalacl /維基/ ScalaCLPlugin。雖然我沒有嘗試過。 –

+0

實際上,你的前3個測試使用了for-comprehensions,並且你正在比較那些與reduce的時間。 –

回答

12

你被告知什麼是真正的關於「爲內涵」,但你的問題,問題是你已經混了「爲內涵」與「匿名函數」。

Scala中的「For comprehension」是一系列.flatMap,.map.filter應用程序的語法糖。由於您正在測試簡化算法,並且由於使用這三個函數不可能實現簡化算法,因此您的測試用例不正確。

下面是一個例子的 「理解力」:

val listOfLists = List(List(1,2), List(3,4), List(5)) 
val result = 
    for { 
    itemOfListOfLists <- listOfLists 
    itemOfItemOfListOfLists <- itemOfListOfLists 
    } 
    yield (itemOfItemOfListOfLists + 1) 
assert(result == List(2,3,4,5,6)) 

編譯器desugars的理解部分爲以下內容:

val result = 
    listOfLists.flatMap(
    itemOfListOfLists => itemOfListOfLists.map(
     itemOfItemOfListOfLists => itemOfItemOfListOfLists + 1 
    ) 
) 

然後desugars匿名函數的語法:

val result = 
    listOfLists.flatMap(
    new Function1[List[Int], List[Int]] { 
     override def apply(itemOfListOfLists: List[Int]): List[Int] = 
     itemOfListOfLists.map(
      new Function1[Int, Int] { 
      override def apply(itemOfItemOfListOfLists: Int): Int = 
       itemOfItemOfListOfLists + 1 
      } 
     ) 
    } 
) 

從脫離的代碼現在很明顯,每次調用apply(itemOfListOfLists: List[Int]): List[Int]方法時,類都會被實例化。這發生在listOfLists的每個條目。所以你的理解越複雜,你得到的對象就越多。