Estoy interesado en analizar Scala, y tengo una pregunta básica para la que parece que no puedo encontrar una respuesta: en general, ¿hay una diferencia en el rendimiento y el uso de la memoria entre Scala y Java?
Estoy interesado en analizar Scala, y tengo una pregunta básica para la que parece que no puedo encontrar una respuesta: en general, ¿hay una diferencia en el rendimiento y el uso de la memoria entre Scala y Java?
Respuestas:
Scala hace que sea muy fácil usar enormes cantidades de memoria sin darse cuenta. Esto suele ser muy poderoso, pero ocasionalmente puede ser molesto. Por ejemplo, suponga que tiene una matriz de cadenas (llamadas array
) y un mapa de esas cadenas a archivos (llamados mapping
). Suponga que desea obtener todos los archivos que están en el mapa y provienen de cadenas de longitud mayor que dos. En Java, podrías
int n = 0;
for (String s: array) {
if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
if (s.length <= 2) continue;
bigEnough[n++] = map.get(s);
}
¡Uf! Trabajo duro. En Scala, la forma más compacta de hacer lo mismo es:
val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)
¡Fácil! Pero, a menos que esté bastante familiarizado con el funcionamiento de las colecciones, lo que quizás no se dé cuenta es que esta forma de crear una matriz intermedia adicional (con filter
) y un objeto adicional para cada elemento de la matriz (conmapping.get
, que devuelve una opción). También crea dos objetos de función (uno para el filtro y otro para el flatMap), aunque eso rara vez es un problema importante ya que los objetos de función son pequeños.
Básicamente, el uso de memoria es, en un nivel primitivo, el mismo. Pero las bibliotecas de Scala tienen muchos métodos poderosos que le permiten crear enormes cantidades de objetos (generalmente de corta duración) con mucha facilidad. El recolector de basura generalmente es bastante bueno con ese tipo de basura, pero si te olvidas por completo de qué memoria se está usando, probablemente tendrás problemas antes en Scala que en Java.
Tenga en cuenta que el código de Computer Language Benchmark Game Scala está escrito en un estilo bastante similar a Java para obtener un rendimiento similar a Java y, por lo tanto, tiene un uso de memoria similar a Java. Puede hacer esto en Scala: si escribe su código para que parezca un código Java de alto rendimiento, será un código Scala de alto rendimiento. (Es posible que pueda escribirlo en un estilo Scala más idiomático y aún así obtener un buen rendimiento, pero depende de los detalles).
Debo agregar que, por la cantidad de tiempo que dedico a la programación, mi código Scala suele ser más rápido que mi código Java, ya que en Scala puedo hacer las tediosas partes no críticas para el rendimiento con menos esfuerzo, y dedicar más atención a optimizar los algoritmos y código para las partes críticas de rendimiento.
Soy un usuario nuevo, por lo que no puedo agregar un comentario a la respuesta de Rex Kerr anterior (permitir a los usuarios nuevos "responder" pero no "comentar" es una regla muy extraña por cierto).
Me inscribí simplemente para responder a la insinuación de "respuesta, la respuesta popular de Rex anterior". Si bien, por supuesto, puede escribir código Scala más conciso, el ejemplo de Java dado está claramente hinchado. La mayoría de los desarrolladores de Java codificarían algo como esto:
List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
if(s.length() > 2 && mapping.get(s) != null) {
bigEnough.add(mapping.get(s));
}
}
Y, por supuesto, si vamos a pretender que Eclipse no hace la mayor parte del tipeo real por usted y que cada personaje guardado realmente lo convierte en un mejor programador, entonces podría codificar esto:
List b=new ArrayList();
for(String s:array)
if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));
Ahora no solo ahorré el tiempo que me llevó escribir nombres de variables completos y llaves (liberándome para pasar 5 segundos más para pensar pensamientos algorítmicos profundos), sino que también puedo ingresar mi código en concursos de ofuscación y potencialmente ganar dinero extra por las vacaciones.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
Escriba su Scala como Java, y puede esperar que se emita un código de bytes casi idéntico, con métricas casi idénticas.
Escríbalo más "idiomáticamente", con objetos inmutables y funciones de orden superior, y será un poco más lento y un poco más grande. La única excepción a esta regla general es cuando se usan objetos genéricos en los que los parámetros de tipo usan la @specialised
anotación, esto creará un código de bytes aún más grande que puede superar el rendimiento de Java al evitar el boxeo / unboxing.
También vale la pena mencionar el hecho de que más memoria / menos velocidad es una compensación inevitable al escribir código que se puede ejecutar en paralelo. El código de Idiomatic Scala es mucho más declarativo en naturaleza que el código típico de Java, y a menudo está a solo 4 caracteres ( .par
) de ser completamente paralelo.
Así que si
¿Diría entonces que el código Scala ahora es comparativamente un 25% más lento o 3 veces más rápido?
La respuesta correcta depende exactamente de cómo define "rendimiento" :)
.par
está en 2.9.
.par
.
map
método será muy pequeña.
Juego de puntos de referencia del lenguaje informático:
Prueba de velocidad java / scala 1.71 / 2.25
Prueba de memoria java / scala 66.55 / 80.81
Entonces, estos puntos de referencia dicen que Java es un 24% más rápido y Scala utiliza un 21% más de memoria.
En general, no es gran cosa y no debería importar en las aplicaciones del mundo real, donde la mayor parte del tiempo la consumen la base de datos y la red.
En pocas palabras: si Scala hace que usted y su equipo (y las personas que asuman el proyecto cuando se vaya) sean más productivos, entonces debería hacerlo.
Otros han respondido esta pregunta con respecto a los bucles cerrados, aunque parece haber una diferencia de rendimiento obvia entre los ejemplos de Rex Kerr que he comentado.
Esta respuesta está realmente dirigida a personas que podrían investigar la necesidad de una optimización de circuito cerrado como falla de diseño.
Soy relativamente nuevo en Scala (aproximadamente un año más o menos), pero la sensación, hasta el momento, es que te permite diferir muchos aspectos del diseño, implementación y ejecución con relativa facilidad (con suficiente lectura de antecedentes y experimentación :)
Características de diseño diferido:
Características de implementación diferida:
Características de ejecución diferida: (lo siento, no hay enlaces)
Estas características, para mí, son las que nos ayudan a recorrer el camino hacia aplicaciones rápidas y ajustadas.
Los ejemplos de Rex Kerr difieren en los aspectos de ejecución diferidos. En el ejemplo de Java, la asignación de memoria se difiere hasta que se calcula su tamaño, donde el ejemplo de Scala difiere la búsqueda de mapeo. Para mí, parecen algoritmos completamente diferentes.
Esto es lo que creo que es más un equivalente de manzanas a manzanas para su ejemplo de Java:
val bigEnough = array.collect({
case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})
Sin colecciones intermedias, sin Option
instancias, etc. Esto también preserva el tipo de colección, por lo que la implementación del bigEnough
tipo es Array[File]
- probablemente hará algo similar a lo que hace el código Java del Sr. Kerr.Array
collect
Las características de diseño diferido que enumeré anteriormente también permitirían a los desarrolladores de API de recopilación de Scala implementar esa rápida implementación de recopilación específica de matriz en futuras versiones sin romper la API. Esto es a lo que me refiero con pisar el camino a la velocidad.
También:
val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)
El withFilter
método que he usado aquí en lugar de filter
soluciona el problema de la colección intermedia, pero todavía existe el problema de la instancia de Opción.
Un ejemplo de velocidad de ejecución simple en Scala es con el registro.
En Java podríamos escribir algo como:
if (logger.isDebugEnabled())
logger.debug("trace");
En Scala, esto es solo:
logger.debug("trace")
porque el parámetro del mensaje para depurar en Scala tiene el tipo " => String
" que considero una función sin parámetros que se ejecuta cuando se evalúa, pero que la documentación llama pass-by-name.
EDITAR {Las funciones en Scala son objetos, por lo que hay un objeto adicional aquí. Para mi trabajo, el peso de un objeto trivial vale la pena eliminar la posibilidad de que un mensaje de registro se evalúe innecesariamente. }
Esto no hace que el código sea más rápido, pero sí es más probable que sea más rápido y es menos probable que tengamos la experiencia de revisar y limpiar el código de otras personas en masa.
Para mí, este es un tema constante dentro de Scala.
El código duro no puede capturar por qué Scala es más rápido, aunque insinúa un poco.
Siento que es una combinación de reutilización de código y el límite de calidad de código en Scala.
En Java, el código impresionante a menudo se ve obligado a convertirse en un desastre incomprensible y, por lo tanto, no es realmente viable dentro de las API de calidad de producción, ya que la mayoría de los programadores no podrían usarlo.
Tengo grandes esperanzas de que Scala pueda permitir que los einsteins entre nosotros implementen API mucho más competentes, potencialmente expresadas a través de DSL. Las API principales en Scala ya están muy lejos en este camino.
Presentación de @higherkinded sobre el tema - Consideraciones de rendimiento de Scala que hace algunas comparaciones de Java / Scala.
Herramientas:
Gran blog:
Java y Scala se compilan en código de bytes JVM, por lo que la diferencia no es tan grande. La mejor comparación que puede obtener es probablemente en el juego de pruebas de lenguaje de computadora , que esencialmente dice que Java y Scala tienen el mismo uso de memoria. Scala es solo un poco más lento que Java en algunos de los puntos de referencia enumerados, pero eso podría deberse simplemente a que la implementación de los programas es diferente.
Sin embargo, ambos están tan cerca que no vale la pena preocuparse. El aumento de productividad que obtienes al usar un lenguaje más expresivo como Scala vale mucho más que el impacto mínimo (si lo hay) en el rendimiento.
Java and Scala both compile down to JVM bytecode,
que se combinó con un so
enunciado diffence isn't that big.
que quería mostrar, que so
es solo un truco retórico y no una conclusión argumentativa.
El ejemplo de Java realmente no es una expresión idiomática para los programas de aplicación típicos. Tal código optimizado se puede encontrar en un método de biblioteca del sistema. Pero luego usaría una matriz del tipo correcto, es decir, Archivo [] y no arrojaría una IndexOutOfBoundsException. (Diferentes condiciones de filtro para contar y sumar). Mi versión sería (siempre (!) Con llaves porque no me gusta pasar una hora buscando un error que se introdujo al guardar los 2 segundos para presionar una sola tecla en Eclipse):
List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
if(s.length() > 2) {
File file = mapping.get(s);
if (file != null) {
bigEnough.add(file);
}
}
}
Pero podría traerle muchos otros ejemplos de código Java feos de mi proyecto actual. Intenté evitar el estilo común de codificación de copiar y modificar factorizando estructuras y comportamientos comunes.
En mi clase base DAO abstracta, tengo una clase interna abstracta para el mecanismo de almacenamiento en caché común. Para cada tipo de objeto de modelo concreto hay una subclase de la clase base DAO abstracta, en la que la clase interna se subclasifica para proporcionar una implementación para el método que crea el objeto comercial cuando se carga desde la base de datos. (No podemos usar una herramienta ORM porque accedemos a otro sistema a través de una API propietaria).
Este código de subclasificación e instanciación no está del todo claro en Java y sería muy legible en Scala.