scala - tag - tecnicas seo on page
¿Dónde mirar primero al optimizar el código de Scala? (2)
¿Su código instancia una gran cantidad de objetos cuando se ejecuta? Las clases de case
Scala, por ejemplo, o las cadenas de map
/ map
flatMap
pueden dar como resultado la flatMap
de un gran número de objetos "innecesarios". Esto puede ralentizar el código e imponer más trabajo en el recolector de basura.
Actualmente necesito optimizar una implementación de Scala de un algoritmo que es demasiado lento. Se implementa de forma funcional, usa solo valores ( val
) y estructuras de datos inmutables. Estoy en el punto en el que ya memoricé funciones importantes (por lo que hay algunos mapas mutables en mi código), lo que hizo que mi código sea el doble de rápido y me pregunto qué hacer a continuación.
Por lo tanto, no estoy buscando consejos genéricos sobre la optimización del software (por ejemplo, optimizar su algortihm primero, usar un generador de perfiles, hacer benchmarks ...), sino más bien consejos específicos de Scala o de optimización de JVM .
Mi pregunta es, por lo tanto, ¿ dónde mirar primero cuando trato de optimizar el código de Scala ? ¿Cuáles son los constructos o patrones de lenguaje común que generalmente causan ralentizaciones?
En particular, estoy buscando consejo sobre los siguientes puntos:
- Leí que
for(...)
construcciones son lentas porque se genera una clase anónima cada vez que se ejecuta el cuerpo del ciclo. Es verdad ? ¿Hay otros lugares donde se genera una clase anónima? (por ejemplo, al usarmap()
con una función anónima) - ¿Son las colecciones inmutables significativamente más lentas que las colecciones mutables en el caso general (especialmente cuando se trata de estructuras de mapas)?
- ¿Hay diferencias de rendimiento significativas entre Scala 2.8, 2.9 y 2.10?
También tuve que optimizar un montón de código Scala en el pasado. Lo siguiente no pretende ser una lista completa, solo unas pocas observaciones prácticas que pueden serle útiles:
Sí, reemplazar un ciclo
for
por unwhile
es más rápido, incluso con Scala 2.10. Consulte la conversación enlazada en los comentarios para obtener más detalles sobre eso. Además, tenga en cuenta que usar "para filtrar" (una condición que sigue a la colección que está iterando) dará lugar a la casilla / unboxing de su condición, lo que puede tener un gran impacto en el rendimiento ( consulte esta publicación para obtener más información ).La pregunta inmutable vs. mutable simplemente se responde por el número de actualizaciones que tiene que realizar y es difícil (para mí) dar una respuesta general aquí.
Hasta ahora, no he observado diferencias de rendimiento significativas entre 2.8, 2.9 y 2.10. Pero claramente esto depende del problema en cuestión. Por ejemplo, si su algoritmo hace un uso intensivo de Range.sum, observará grandes diferencias (porque ahora es O (1) en 2.10).
Observé que el uso de la colección correspondiente de Java en lugar de la versión de Scala también puede generar aceleraciones significativas (como estadio de juego, diría del orden del 5-10%). Por ejemplo, tuve los siguientes resultados (que se muestran son tiempos de ejecución) en un microbenchmark para un problema muy específico (nota: no generalice desde eso, ejecute el suyo propio).
ColtBitVector min: 0.042 avg: 0.245 max: 40.120 JavaBitSet min: 0.043 avg: 0.165 max: 4.306 JavaHashSet min: 0.191 avg: 0.716 max: 12.624 JavaTreeSet min: 0.313 avg: 1.428 max: 64.504 ScalaBitSetImmutable min: 0.380 avg: 1.675 max: 13.838 ScalaBitSetMutable min: 0.423 avg: 3.693 max: 457.146 ScalaSetImmutable min: 0.458 avg: 2.305 max: 9.998 ScalaSetMutable min: 0.340 avg: 1.332 max: 10.974
El problema en cuestión era calcular una intersección simple de conjuntos enteros (con un tamaño y número de conjuntos muy específicos). Lo que quiero demostrar: ¡elegir la colección correcta / incorrecta puede tener un impacto significativo! De nuevo, creo que es difícil dar un consejo general sobre qué tipos de datos elegir, ya que esto solo nos dice el rendimiento en este problema especial de intersección (pero sí elegí el
HashSet
de Java en algunos casos sobre las alternativas). Además, tenga en cuenta que este problema de intersección no requiere un tipo de datos mutable. Sin embargo, puede haber diferencias de rendimiento incluso en la funcionalidad inmutable (y considerando queSet
fue la colección mutable que fue significativamente más rápida, es la inmutable paraBitSet
). Por lo tanto, dependiendo de la situación, es posible que desee elegir una colección mutable sobre una inmutable para obtener el máximo rendimiento (¡utilícela con cuidado!).Me dijeron que declarar una variable
private[this] var foo = ...
evita la creación de funciones getter / setter y debería ser más rápida (descargo de responsabilidad: nunca confirmé eso en una microbenchmark).Cuando se trata de tipos genéricos, la definición de la versión de
@specialized
para tipos específicos debería dar como resultado una aceleración.Aunque trato de evitar generalizaciones, podría vivir con lo siguiente: Trate de usar Arrays nativos. En muchos de mis puntos de referencia, terminé usando Arrays, lo cual tiene sentido considerando su implementación en la JVM.
Un pequeño punto que me viene a la mente:
origCollection.toSomeCollectionName
diferencias en la construcción de colecciones, ya sea pororigCollection.toSomeCollectionName
sobre la construcción manual y la construcción usando el objeto complementario (es decir,SomeCollectionName(origCollection :_*)
). En muchos casos, el último fue significativamente más rápido.