Dlaczego Scala „dla pętli” jest tak powolna w porównaniu do pętli FOR?

Mówi się, że Scala dla wyrażeń jest właściwie dość powolny. Powodem, dla którego mi podano, było to, że ze względu na ograniczenia Java, dla wyrażeń (takich jak „zmniejsz”, używanych poniżej), należy wygenerować tymczasowy obiekt z każdą iteracją, aby wywołać przekazaną funkcję.

CZY TO PRAWDA? Poniższe testy wydają się to potwierdzać, ale nie do końca rozumiem, dlaczego tak się dzieje.

Może to mieć sens dla funkcji „lambd” lub anonimowych, ale nie dla funkcji nieanonimowych.

W moim teście uruchomiłem pętle przeciwko list.reduce (zobacz kod poniżej) i stwierdziłem, że są one dwa razy szybsze, nawet gdy każda iteracja nazywała dokładnie tę samą funkcję, która została przekazana do zmniejszenia!

Uważam to za zdumiewająco sprzeczne z intuicją (kiedyś pomyślałbym, że biblioteka Scala zostałaby starannie stworzona, aby była jak najbardziej optymalna).

W teście, który razem wziąłem, uruchomiłem tę samą pętlę (sumując liczby od 1 do 1 miliona, niezależnie od przepełnienia) pięć różnych sposobów:

dla pętli na tablicy wartościfor loop, ale wywołanie funkcji zamiast inline arithmeticdla pętli, tworząc obiekt zawierający funkcję dodawanialist.reduce, przekazując mi anonimową funkcjęlist.reduce, przekazując funkcję członka obiektu

Wyniki były następujące: test: min / max / średnia (milisekundy)

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

Jak można zauważyć, wersje „do zrozumienia” są w kolejności „z nowymi dla każdej instancji”, co oznacza, że ​​„nowy” może w rzeczywistości być wykonywany zarówno dla anonimowych, jak i nieanonimowych wersji funkcji.

Metodologia: poniższy kod (usunięto połączenie testowe) został skompilowany w pojedynczy plik .jar, aby upewnić się, że wszystkie wersje mają ten sam kod biblioteki. Każdy test w każdej iteracji został wywołany w nowej maszynie JVM (tj. Scala -cp ... dla każdego testu) w celu usunięcia problemów z wielkością sterty.

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))
                    }
         ...
}

questionAnswers(1)

yourAnswerToTheQuestion