¿Por qué Scala "para la comprensión de los bucles" es muy lento en comparación con los bucles FOR?

Se ha dicho que Scala para la comprensión es en realidad bastante lento. La razón por la que me dieron fue que debido a una limitación de Java, las comprensiones (como "reducir", que se usan a continuación) necesitan generar un objeto temporal con cada iteración para poder llamar a la función pasada.

¿ES ESTO CIERTO? Las pruebas a continuación parecen confirmar esto, pero no entiendo completamente por qué este es el caso.

Esto podría tener sentido para "lambdas" o funciones anónimas, pero no para funciones no anónimas.

En mi prueba, corrí para bucles contra list.reduce (vea el código a continuación), ¡y encontré que eran más de dos veces más rápidos, incluso cuando cada iteración llamaba exactamente la misma función que se pasó para reducir!

Encuentro esto sorprendentemente contrario a la intuición (una vez pensaría que la biblioteca de Scala se habría creado cuidadosamente para que fuera lo más óptima posible).

En una prueba que armé, corrí el mismo bucle (resumiendo los números 1 a un millón, independientemente del desbordamiento) de cinco maneras diferentes:

para bucle sobre la matriz de valorespara bucle, pero llamando a una función en lugar de aritmética en líneapara bucle, creando un objeto que contiene una función de sumalist.reduce, pasando i una función anónimalist.reduce, pasando en una función miembro objeto

Los resultados fueron los siguientes: prueba: mín / máx / promedio (milisegundos)

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

Como puede verse, las versiones "para comprensión" están en el orden de "para con nuevo para cada instancia", lo que implica que se puede realizar un "nuevo" para las versiones de funciones anónimas y no anónimas.

Metodología: el código a continuación (llamada de prueba eliminada) se compiló en un solo archivo .jar para garantizar que todas las versiones ejecutaran el mismo código de biblioteca. Cada prueba en cada iteración fue llamada en una nueva JVM (es decir, scala -cp ... para cada prueba) para eliminar los problemas de tamaño del montón.

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

Respuestas a la pregunta(1)

Su respuesta a la pregunta