Scala vs Java, rendimiento y memoria? [cerrado]

160

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?

John Smith
fuente
3
He escuchado afirmaciones de que el rendimiento puede ser muy cercano. Sospecho que depende mucho de lo que estás haciendo. (como lo es para Java vs C)
Peter Lawrey 05 de
La respuesta a este tipo de preguntas es "depende": para prácticamente cualquier comparación del sistema X con el sistema Y. Además, este es un duplicado de stackoverflow.com/questions/2479819/…
James Moore

Respuestas:

261

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.

Rex Kerr
fuente
172
+1 para ese párrafo final. Es un punto vital que se deja fuera de consideración con demasiada frecuencia.
Kevin Wright
2
Pensé que las opiniones podrían ayudar mucho con los problemas que mencionas. ¿O no es cierto con las matrices, específicamente?
Nicolas Payette
1
@Kevin Wright - "Es un punto vital que se deja fuera de consideración con demasiada frecuencia" - Es algo fácil de decir y difícil de demostrar, y nos dice algo sobre las habilidades de Rex Kerr y no lo que otros menos hábiles logran.
igouy
1
>> en estilo idiomático con rendimiento superlativo << Hay espacio en el juego de puntos de referencia para los programas idiomáticos de Scala que no logran un rendimiento "superlativo".
igouy
2
@RexKerr: ¿su ejemplo de Java no busca la clave de mapeo dos veces para cada Cadena posible, donde su ejemplo de Scala solo lo hace una vez que se han seleccionado las Cadenas? Es decir, ¿están optimizados de diferentes maneras para diferentes conjuntos de datos?
Septiembre
103

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.

No durmiendo
fuente
77
¿Cómo es que no eres miembro del club del "lenguaje moderno del mes"? Buenos comentarios. Disfruté especialmente leyendo el último párrafo.
Stepanian
21
Excelentemente puesto! Me canso de los ejemplos artificiales en los que el código inflado de Java es seguido por un ejemplo breve y cuidadosamente construido de Scala (o algún otro lenguaje FP) y luego una conclusión apresurada de que Scala debe ser mejor que Java debido a ello. ¡Quién escribió algo significativo en Scala de todos modos! ;-) Y no digas Twitter ...
chrisjleu
2
Bueno, la solución de Rex asigna previamente la memoria para la matriz, lo que hará que el código compilado se ejecute más rápido (porque con su enfoque, permite que la JVM reasigne periódicamente su matriz a medida que crece). Aunque hubo más tipeo involucrado, en términos de rendimiento podría ser un ganador.
Ashalynd
55
mientras lo hacemos, en java8 será:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl
2
Lo que hace que Scala sea "mejor" en algunos aspectos que Java son las capacidades ampliadas del sistema de tipos que hacen que sea más fácil expresar patrones más genéricos como tipos (como Mónadas, Functores, etc.). Esto le permite crear tipos que no se interponen en su camino debido a contratos demasiado estrictos, como sucede a menudo en Java. Los contratos estrictos que no se basan en patrones reales en el código son la razón por la cual los patrones de inversión de responsabilidad son necesarios solo para probar adecuadamente su código (la inyección de dependencia viene a la mente primero y el infierno XML que trae). El addl. la concisión que aporta la flexibilidad es solo una ventaja.
josiah
67

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 @specialisedanotació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

  • El código Scala tarda 1.25 veces más que el código Java en un solo hilo
  • Se puede dividir fácilmente en 4 núcleos (ahora común incluso en computadoras portátiles)
  • para un tiempo de ejecución paralelo de (1.24 / 4 =) 0.3125x el Java original

¿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" :)

Kevin Wright
fuente
44
Por cierto, es posible que desee mencionar que .parestá en 2.9.
Rex Kerr
26
>> ¿Diría entonces que el código Scala ahora es comparativamente un 25% más lento o 3 veces más rápido? << Yo diría ¿por qué no es su comparación hipotética con el código Java multiproceso?
igouy
17
@igouy: el punto es que dicho código hipotético no existe, la naturaleza imperativa del código Java "más rápido" hace que sea mucho más difícil de paralelizar, de modo que la relación costo / beneficio significa que es poco probable que suceda. La Scala idiomática, por otro lado, siendo mucho más declarativa en su naturaleza, con frecuencia puede hacerse concurrente con nada más que un cambio trivial.
Kevin Wright
77
La existencia de programas Java concurrentes no implica que un programa Java típico pueda adaptarse fácilmente a la concurrencia. En todo caso, diría que ese estilo particular de unión de horquilla es particularmente raro en Java y debe codificarse explícitamente, mientras que operaciones simples como encontrar el valor mínimo contenido o la suma de valores en una colección se pueden hacer trivialmente en paralelo en Scala simplemente usando .par.
Kevin Wright
55
No, tal vez no. Este tipo de cosas es un elemento fundamental para muchos algoritmos, y verlo presente en un nivel tan bajo en el lenguaje y las bibliotecas estándar (las mismas bibliotecas estándar que usarán todos los programas, no solo las típicas) es evidencia de que usted ' Ya estás más cerca de ser concurrente simplemente eligiendo el idioma. Por ejemplo, el mapeo sobre una colección es inherentemente adecuado para la paralelización, y la cantidad de programas Scala que no usan el mapmétodo será muy pequeña.
Kevin Wright
31

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.

Peter Knego
fuente
34
Tamaño del código java / scala 3.39 / 2.21
hammar
22
Tenga cuidado con números como estos, suenan terriblemente precisos, mientras que en realidad no significan casi nada. No es como si Scala fuera siempre un 24% más rápido que Java en promedio, etc.
Jesper,
3
Los números citados por Afaik indican lo contrario: Java es un 24% más rápido que scala. Pero como dices, son microbenchmarks, que no necesitan coincidir con lo que está sucediendo en las aplicaciones reales. Y la forma diferente o la solución del problema en diferentes idiomas podría conducir a programas menos comparables al final.
usuario desconocido
9
"Si Scala te hace a ti y a tu equipo ..." Conclusión: lo sabrás después y no antes :-)
igouy
La página de ayuda del juego de puntos de referencia proporciona un ejemplo de cómo "Comparar la velocidad y el tamaño del programa para implementaciones de 2 idiomas". Para Scala y Java, la página web de comparación adecuada es: shootout.alioth.debian.org/u64q/scala.php
igouy
20

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)

  • Valores perezosos seguros para subprocesos
  • Pase por nombre
  • Cosas monádicas

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 Optioninstancias, etc. Esto también preserva el tipo de colección, por lo que la implementación del bigEnoughtipo es Array[File]- probablemente hará algo similar a lo que hace el código Java del Sr. Kerr.Arraycollect

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 withFiltermétodo que he usado aquí en lugar de filtersoluciona 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.

Seth
fuente
Su material de registro es un buen ejemplo de las dificultades de rendimiento de Scala: logger.debug ("trace") crea un nuevo objeto para la función sin parámetros.
jcsahnwaldt Restablece a Monica
De hecho, ¿cómo afecta eso a mi punto asociado?
Seth
Los objetos antes mencionados también se pueden usar para hacer estructuras de control de IoC transparentes en aras de la eficiencia. Sí, el mismo resultado es teóricamente posible en Java, pero sería algo que afectó / ofuscó drásticamente la forma en que se escribe el código, de ahí mi argumento de que la habilidad de Scala para diferir muchos elementos del desarrollo de software nos ayuda a avanzar hacia un código más rápido. más rápido en la práctica frente a tener un rendimiento de la unidad marginalmente más rápido.
Septiembre
Ok, he vuelto a leer esto y escribí, "velocidad de ejecución simple". Agregaré una nota. Buen punto :)
Septiembre
3
Sentencia if predecible (básicamente gratis en un procesador superescalar) vs asignación de objetos + basura. El código de Java es obviamente más rápido (tenga en cuenta que solo evalúa la condición, la ejecución no alcanzará la declaración de registro). En respuesta a "Para mi trabajo, vale la pena eliminar el peso de un objeto trivial la posibilidad de que un mensaje de registro se evalúe innecesariamente ".
Eloff
10

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.

ryeguy
fuente
77
Veo una falacia lógica aquí: ambos lenguajes se compilan en bytecode, pero un programador experimentado y un novato, su código también se compila en bytecode, pero no en el mismo bytecode, así que la conclusión es que la diferencia no puede ser tan grande , puede estar equivocado Y, de hecho, en tiempos anteriores, un ciclo while podría ser mucho, mucho más rápido en escala que un ciclo for semánticamente equivalente (si no recuerdo mal, hoy es mucho mejor). Y ambos fueron compilados a bytecode, por supuesto.
usuario desconocido
@user unknown: "un bucle while podría ser mucho, mucho más rápido en escala que un bucle for semánticamente equivalente". Observe que esos programas de juego de referencia Scala se escriben con bucles while.
igouy
@igouy: No hablé sobre los resultados de este microbenchmark, sino sobre la argumentación. Una declaración verdadera Java and Scala both compile down to JVM bytecode, que se combinó con un soenunciado diffence isn't that big.que quería mostrar, que soes solo un truco retórico y no una conclusión argumentativa.
usuario desconocido
3
respuesta sorprendentemente incorrecta con votos sorprendentemente altos.
shabunc
4

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.

MickH
fuente