Warum ist Scala für das Verständnis von Schleifen im Vergleich zu FOR-Schleifen so langsam?

Es wurde gesagt, dass das Scala For-Verständnis eigentlich recht langsam ist. Der Grund, warum ich angegeben wurde, war, dass aufgrund einer Java-Einschränkung für das unten verwendete Verständnis (z. B. "Reduzieren") bei jeder Iteration ein temporäres Objekt generiert werden muss, um die übergebene Funktion aufzurufen.

IST DAS WAHR? Die folgenden Tests scheinen dies zu belegen, aber ich verstehe nicht vollständig, warum dies der Fall ist.

Dies ist möglicherweise sinnvoll für "Lambdas" oder anonyme Funktionen, jedoch nicht für nicht anonyme Funktionen.

In meinem Test habe ich list.reduce auf Schleifen überprüft (siehe Code unten) und festgestellt, dass sie doppelt so schnell sind, auch wenn jede Iteration genau dieselbe Funktion aufrief, die zum Reduzieren übergeben wurde!

Ich finde das erstaunlich kontraintuitiv (ich hätte einmal gedacht, die Scala-Bibliothek wäre sorgfältig erstellt worden, um so optimal wie möglich zu sein).

In einem Test, den ich zusammengestellt habe, habe ich dieselbe Schleife (summiere die Zahlen 1 bis 1 Million, unabhängig vom Überlauf) auf fünf verschiedene Arten durchlaufen:

for Schleife über das Array von Wertenfor-Schleife, aber Aufrufen einer Funktion anstelle von Inline-Arithmetikfor-Schleife: Erstellt ein Objekt, das eine Additionsfunktion enthältlist.reduce, übergebe ich eine anonyme Funktionlist.reduce, Übergabe einer Objektelementfunktion

Die Ergebnisse waren wie folgt: Test: min / max / Durchschnitt (Millisekunden)

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

Wie zu sehen ist, sind die "zum Verständnis" -Versionen in der Reihenfolge "für mit neu für jede Instanz", was impliziert, dass ein "neu" tatsächlich sowohl für die anonymen als auch für die nicht anonymen Funktionsversionen durchgeführt werden kann.

Methodik: Der folgende Code (Testaufruf entfernt) wurde in eine einzige JAR-Datei kompiliert, um sicherzustellen, dass alle Versionen denselben Bibliothekscode ausführten. Jeder Test in jeder Iteration wurde in einer neuen JVM (d. H. Scala -cp ... für jeden Test) aufgerufen, um Probleme mit der Heap-Größe zu beseitigen.

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

Antworten auf die Frage(1)

Ihre Antwort auf die Frage