performance - puede - tipos de conversiones fisica
¿Cuál es el impacto en el rendimiento de las conversiones de tipo implícitas de Scala? (2)
En Scala, ¿hay un impacto significativo en la CPU o en la memoria al usar conversiones de tipo implícito para aumentar la funcionalidad de una clase frente a otras opciones de implementación posibles?
Por ejemplo, considere una tonta función de manipulación de cadenas. Esta implementación utiliza concatenación de cadenas:
object Funky {
def main(args: Array[String]) {
args foreach(arg => println("Funky " + arg))
}
}
Esta implementación oculta la concatenación detrás de un método miembro mediante el uso de una conversión de tipo implícito:
class FunkyString(str: String) {
def funkify() = "Funky " + str
}
object ImplicitFunky {
implicit def asFunkyString(str: String) = new FunkyString(str)
def main(args: Array[String]) {
args foreach(arg => println(arg.funkify()))
}
}
Ambos hacen lo mismo:
scala> Funky.main(Array("Cold Medina", "Town", "Drummer"))
Funky Cold Medina
Funky Town
Funky Drummer
scala> ImplicitFunky.main(Array("Cold Medina", "Town", "Drummer"))
Funky Cold Medina
Funky Town
Funky Drummer
¿Hay alguna diferencia de rendimiento? Algunas consideraciones específicas:
¿Invoca Scala las llamadas implícitas al método asFunkyString?
¿Scala crea realmente un nuevo objeto FunkyString envoltorio para cada argumento, o puede optimizar las asignaciones de objetos adicionales?
Supongamos que FunkyString tenía 3 métodos diferentes (funkify1, funkify2 y funkify3), y el cuerpo de foreach llamó a cada uno en sucesión:
println(arg.funkify1())
println(arg.funkify2())
println(arg.funkify3())
¿Repetiría Scala la conversión 3 veces, o optimizaría las conversiones redundantes y solo lo haría una vez para cada iteración de bucle?
Supongamos, en cambio, que capturo explícitamente la conversión en otra variable, como esta:
val fs = asFunkyString(arg)
println(fs.funkify1())
println(fs.funkify2())
println(fs.funkify3())
¿Eso cambia la situación?
En términos prácticos, ¿el uso generalizado de conversiones implícitas es un problema de rendimiento potencial o es típicamente inofensivo?
Traté de configurar una microbenchmark usando la excelente Scala-Benchmark-Template .
Es muy difícil escribir un punto de referencia significativo (no optimizado por el JIT) que pruebe solo las conversiones implícitas, así que tuve que agregar un poco de sobrecarga.
Aquí está el código:
class FunkyBench extends SimpleScalaBenchmark {
val N = 10000
def timeDirect( reps: Int ) = repeat(reps) {
var strs = List[String]()
var s = "a"
for( i <- 0 until N ) {
s += "a"
strs ::= "Funky " + s
}
strs
}
def timeImplicit( reps: Int ) = repeat(reps) {
import Funky._
var strs = List[String]()
var s = "a"
for( i <- 0 until N ) {
s += "a"
strs ::= s.funkify
}
strs
}
}
Y aquí están los resultados:
[info] benchmark ms linear runtime
[info] Direct 308 =============================
[info] Implicit 309 ==============================
Mi conclusión: en cualquier pieza de código no trivial, el impacto de las conversiones implícitas (creación de objetos) no es medible.
EDITAR: utilicé scala 2.9.0 y java 1.6.0_24 (en modo servidor)
JVM puede optimizar las asignaciones de objetos adicionales, si detecta que valdría la pena.
Esto es importante, porque si solo alineas las cosas, obtendrás métodos más grandes, lo que podría causar problemas de rendimiento con el caché o incluso disminuir la posibilidad de que JVM aplique otras optimizaciones.