Rendimiento de Scala en comparación con Java

41

En primer lugar, me gustaría dejar en claro que esta no es una pregunta de lenguaje X versus lenguaje Y para determinar cuál es mejor.

He estado usando Java durante mucho tiempo y tengo la intención de seguir usándolo. Paralelo a esto, actualmente estoy aprendiendo Scala con gran interés: aparte de cosas menores que me llevan un tiempo acostumbrarme a mi impresión, es que realmente puedo trabajar muy bien en este idioma.

Mi pregunta es: ¿cómo se compara el software escrito en Scala con el software escrito en Java en términos de velocidad de ejecución y consumo de memoria? Por supuesto, esta es una pregunta difícil de responder en general, pero esperaría que las construcciones de nivel superior, como la coincidencia de patrones, las funciones de orden superior, etc., introduzcan algunos gastos generales.

Sin embargo, mi experiencia actual en Scala se limita a pequeños ejemplos bajo 50 líneas de código y no he ejecutado ningún punto de referencia hasta ahora. Entonces, no tengo datos reales.

Si resultó que Scala tiene algo de sobrecarga de Java, ¿tiene sentido tener proyectos mixtos de Scala / Java, donde uno codifica las partes más complejas en Scala y las partes críticas para el rendimiento en Java? ¿Es esta una práctica común?

EDITAR 1

He ejecutado un pequeño punto de referencia: construya una lista de enteros, multiplique cada entero por dos y colóquelo en una nueva lista, imprima la lista resultante. Escribí una implementación de Java (Java 6) y una implementación de Scala (Scala 2.9). He ejecutado ambos en Eclipse Indigo en Ubuntu 10.04.

Los resultados son comparables: 480 ms para Java y 493 ms para Scala (promedio de más de 100 iteraciones). Aquí están los fragmentos que he usado.

// Java
public static void main(String[] args)
{
    long total = 0;
    final int maxCount = 100;
    for (int count = 0; count < maxCount; count++)
    {
        final long t1 = System.currentTimeMillis();

        final int max = 20000;
        final List<Integer> list = new ArrayList<Integer>();
        for (int index = 1; index <= max; index++)
        {
            list.add(index);
        }

        final List<Integer> doub = new ArrayList<Integer>();
        for (Integer value : list)
        {
            doub.add(value * 2);
        }

        for (Integer value : doub)
        {
            System.out.println(value);
        }

        final long t2 = System.currentTimeMillis();

        System.out.println("Elapsed milliseconds: " + (t2 - t1));
        total += t2 - t1;
    }

    System.out.println("Average milliseconds: " + (total / maxCount));
}

// Scala
def main(args: Array[String])
{
    var total: Long = 0
    val maxCount    = 100
    for (i <- 1 to maxCount)
    {
        val t1   = System.currentTimeMillis()
        val list = (1 to 20000) toList
        val doub = list map { n: Int => 2 * n }

        doub foreach ( println )

        val t2 = System.currentTimeMillis()

        println("Elapsed milliseconds: " + (t2 - t1))
        total = total + (t2 - t1)
    }

    println("Average milliseconds: " + (total / maxCount))
}

Entonces, en este caso, parece que la sobrecarga de Scala (usando rango, mapa, lambda) es realmente mínima, lo que no está lejos de la información proporcionada por World Engineer.

¿Quizás hay otras construcciones Scala que deberían usarse con cuidado porque son particularmente pesadas de ejecutar?

EDITAR 2

Algunos de ustedes señalaron que las impresiones en los bucles internos ocupan la mayor parte del tiempo de ejecución. Los eliminé y establecí el tamaño de las listas en 100000 en lugar de 20000. El promedio resultante fue de 88 ms para Java y 49 ms para Scala.

Giorgio
fuente
55
Me imagino que dado que Scala compila el código de bytes JVM, entonces el rendimiento podría ser teóricamente equivalente a que Java se ejecute bajo la misma JVM, todas las demás cosas son iguales. Creo que la diferencia está en cómo el compilador Scala crea el código de bytes y si lo hace de manera eficiente.
maple_shaft
2
@maple_shaft: ¿O tal vez hay una sobrecarga en el tiempo de compilación de Scala?
FrustratedWithFormsDesigner
1
@Giorgio No hay distinción de tiempo de ejecución entre los objetos Scala y los objetos Java, todos son objetos JVM que están definidos y se comportan según el código de byte. Scala, por ejemplo, como lenguaje tiene el concepto de cierres, pero cuando se compilan, se compilan en varias clases con código de bytes. Teóricamente, podría escribir físicamente código Java que pudiera compilar exactamente el mismo código de bytes y el comportamiento en tiempo de ejecución sería exactamente el mismo.
maple_shaft
2
@maple_shaft: Eso es exactamente a lo que estoy apuntando: encuentro que el código Scala anterior es mucho más conciso y legible que el código Java correspondiente. Me preguntaba si tendría sentido escribir partes de un proyecto Scala en Java por razones de rendimiento, y cuáles serían esas partes.
Giorgio
2
El tiempo de ejecución estará ocupado en gran medida por las llamadas println. Necesita una prueba más intensiva en cómputo.
Kevin Cline

Respuestas:

39

Hay una cosa que puede hacer de manera concisa y eficiente en Java que no puede hacer en Scala: las enumeraciones. Para todo lo demás, incluso para construcciones que son lentas en la biblioteca de Scala, puede obtener versiones eficientes trabajando en Scala.

Por lo tanto, en su mayor parte, no necesita agregar Java a su código. Incluso para el código que usa la enumeración en Java, a menudo hay una solución en Scala que es adecuada o buena: pongo la excepción en las enumeraciones que tienen métodos adicionales y cuyos valores constantes int se usan.

En cuanto a qué tener cuidado, aquí hay algunas cosas.

  • Si usa el patrón enriquecer mi biblioteca, siempre convierta a una clase. Por ejemplo:

    // WRONG -- the implementation uses reflection when calling "isWord"
    implicit def toIsWord(s: String) = new { def isWord = s matches "[A-Za-z]+" }
    
    // RIGHT
    class IsWord(s: String) { def isWord = s matches "[A-Za-z]+" }
    implicit def toIsWord(s: String): IsWord = new IsWord(s)
    
  • Tenga cuidado con los métodos de recolección: debido a que son polimórficos en su mayor parte, JVM no los optimiza. No necesita evitarlos, pero preste atención en las secciones críticas. Tenga en cuenta que foren Scala se implementa a través de llamadas a métodos y clases anónimas.

  • Si usa una clase Java, como String, Arrayo AnyValclases que corresponden a primitivas Java, prefiera los métodos proporcionados por Java cuando existan alternativas. Por ejemplo, use lengthon Stringy en Arraylugar de size.

  • Evite el uso descuidado de conversiones implícitas, ya que puede encontrarse usando conversiones por error en lugar de por diseño.

  • Extiende las clases en lugar de los rasgos. Por ejemplo, si está extendiendo Function1, extienda en su AbstractFunction1lugar.

  • Uso -optimisey especialización para obtener la mayor parte de Scala.

  • Entiende lo que está sucediendo: javapes tu amigo, y también lo son un montón de banderas Scala que muestran lo que está sucediendo.

  • Los modismos de Scala están diseñados para mejorar la corrección y hacer que el código sea más conciso y fácil de mantener. No están diseñados para la velocidad, por lo que si necesita usarlos en nulllugar de Optionen una ruta crítica, ¡hágalo! Hay una razón por la cual Scala es multi-paradigma.

  • Recuerde que la verdadera medida del rendimiento es ejecutar código. Vea esta pregunta para ver un ejemplo de lo que puede suceder si ignora esa regla.

Daniel C. Sobral
fuente
1
+1: Mucha información útil, incluso sobre temas que todavía tengo que aprender, pero es útil haber leído alguna pista antes de poder verlos.
Giorgio
¿Por qué el primer enfoque utiliza la reflexión? Genera clase anónima de todos modos, entonces, ¿por qué no usarla en lugar de reflexión?
Oleksandr.Bezhan
@ Oleksandr.Bezhan La clase anónima es un concepto de Java, no uno de Scala. Genera un refinamiento de tipo. No se puede acceder desde fuera a un método de clase anónimo que no anule su clase base. Lo mismo no es cierto para los refinamientos de tipo de Scala, por lo que la única forma de llegar a ese método es a través de la reflexión.
Daniel C. Sobral
Esto suena bastante terrible. Especialmente: "Tenga cuidado con los métodos de recolección, ya que son polimórficos en su mayor parte, JVM no los optimiza. No necesita evitarlos, pero preste atención en las secciones críticas".
mate
21

Según el Benchmarks Game para un sistema de núcleo único de 32 bits, Scala tiene una mediana del 80% más rápido que Java. El rendimiento es aproximadamente el mismo para una computadora Quad Core x64. Incluso el uso de la memoria y la densidad del código son muy similares en la mayoría de los casos. Diría que, en base a estos análisis (bastante poco científicos), usted tiene razón al afirmar que Scala agrega algo de sobrecarga a Java. No parece agregar toneladas de sobrecarga, por lo que sospecho que el diagnóstico de artículos de orden superior que ocupan más espacio / tiempo es el más correcto.

Ingeniero mundial
fuente
2
Para esta respuesta, utilice la comparación directa como sugiere la página de Ayuda ( shootout.alioth.debian.org/help.php#comparetwo )
igouy
18
  • El rendimiento de Scala es muy decente si solo escribe código similar a Java / C en Scala. El compilador utilizará primitivas de JVM para Int, Char, etc., cuando se puede. Mientras que los bucles son tan eficientes en Scala.
  • Tenga en cuenta que las expresiones lambda se compilan en instancias de subclases anónimas de las Functionclases. Si pasa una lambda a map, la clase anónima necesita ser instanciada (y algunos locales pueden necesitar ser pasados), y luego cada iteración tiene una sobrecarga de llamadas a funciones adicionales (con algún paso de parámetros) de las applyllamadas.
  • Muchas clases como scala.util.Randomson solo envoltorios alrededor de clases JRE equivalentes. La llamada de función adicional es un poco inútil.
  • Tenga cuidado con las implicidades en el código de rendimiento crítico. java.lang.Math.signum(x)es mucho más directo que x.signum(), lo que convierte a RichInty de regreso.
  • El principal beneficio de rendimiento de Scala sobre Java es la especialización. Tenga en cuenta que la especialización se usa con moderación en el código de la biblioteca.
Daniel Lubarov
fuente
5
  • a) De mi conocimiento limitado, tengo que comentar que el código en el método principal estático no se puede optimizar muy bien. Debe mover el código crítico a una ubicación diferente.
  • b) A partir de largas observaciones, recomendaría no hacer un gran rendimiento en la prueba de rendimiento (excepto que es exactamente lo que desea optimizar, pero ¿quién debería leer 2 millones de valores?). Estás midiendo println, lo cual no es muy interesante. Reemplazando el println con max:
(1 to 20000).toList.map (_ * 2).max

reduce el tiempo de 800 ms a 20 en mi sistema.

  • c) Se sabe que la comprensión anticipada es un poco lenta (aunque tenemos que admitir que está mejorando todo el tiempo). Utilice while o tailrecursive funciones en su lugar. No en este ejemplo, donde está el bucle externo. Utilice la @ tailrec-annotation, para probar la tairecursiveness.
  • d) La comparación con C / Assembler falla. No reescribe el código scala para diferentes arquitecturas, por ejemplo. Otra diferencia importante a las situaciones históricas son
    • Compilador JIT, optimizando sobre la marcha, y tal vez dinámicamente, dependiendo de los datos de entrada
    • La importancia de la memoria caché falla
    • La creciente importancia de la invocación paralela. Scala hoy tiene soluciones para trabajar sin mucha sobrecarga en paralelo. Eso no es posible en Java, excepto que haces mucho más trabajo.
usuario desconocido
fuente
2
He eliminado el println del bucle y en realidad el código Scala es más rápido que el código Java.
Giorgio
La comparación con C y Assembler se entiende en el siguiente sentido: un lenguaje de nivel superior tiene abstracciones más poderosas, pero es posible que deba usar el lenguaje de nivel inferior para el rendimiento. ¿Se mantiene este paralelo considerando a Scala como el lenguaje de nivel superior y Java como el lenguaje de nivel inferior? Tal vez no, ya que Scala parece ofrecer un rendimiento similar a Java.
Giorgio
No creo que importe mucho para Clojure o Scala, pero cuando solía jugar con jRuby y Jython, probablemente habría escrito el código más crítico para el rendimiento en Java. Con esos dos vi una disparidad significativa, pero eso fue hace años ... podría ser mejor.
Aparejo el