Rendimiento de Scala en comparación con Java

39

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

He estado usando Java durante mucho tiempo y tengo la intención de seguir usándolo. Paralelamente a esto, actualmente estoy aprendiendo Scala con gran interés: aparte de las pequeñas cosas que me han costado un poco 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 espero que las construcciones de nivel superior, como la coincidencia de patrones, las funciones de orden superior, etc., introduzcan cierta sobrecarga.

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

Si resultó que Scala tiene un poco de escritura de Java, tiene sentido mezclar proyectos 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?

EDIT 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 Java (Java 6) y una implementación Scala (Scala 2.9). He ejecutado ambos en Eclipse Indigo bajo Ubuntu 10.04.

Los resultados son comparables: 480 ms para Java y 493 ms para Scala (promediado en 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 range, map, lambda) es realmente mínima, que no está muy lejos de la información proporcionada por Ingeniero mundial.

Tal vez hay otras construcciones de Scala que deben usarse con cuidado porque son pesadas para ejecutar.

EDIT 2

Algunos de ustedes señalaron que la impresión en los bucles internos ocupa la mayor parte de El tiempo de ejecución. Los quité 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.

    
pregunta Giorgio 24.01.2012 - 19:47

5 respuestas

37

Hay una cosa que puedes hacer de manera concisa y eficiente en Java que no puedes en Scala: enumeración. 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 utiliza la enumeración en Java, a menudo hay una solución en Scala que es adecuada o buena: coloco la excepción en las enumeraciones que tienen métodos adicionales y cuyos valores constantes constantes se usan.

En cuanto a lo que hay que vigilar, aquí hay algunas cosas.

  • Si usa el patrón de enriquecer mi biblioteca, siempre conviértalo 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 recopilación, ya que en su mayoría son polimórficos, JVM no los optimiza. No es necesario que los evites, sino que debes prestarle atención en las secciones críticas. Tenga en cuenta que for en Scala se implementa a través de llamadas a métodos y clases anónimas.

  • Si utiliza una clase Java, como las clases String , Array o AnyVal que corresponden a primitivas de Java, prefiera los métodos proporcionados por Java cuando existen alternativas. Por ejemplo, use length en String y Array en lugar de size .

  • Evita el uso descuidado de las conversiones implícitas, ya que puedes encontrarte usando las conversiones por error en lugar de por diseño.

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

  • Use -optimise y la especialización para obtener la mayor parte de Scala.

  • Comprende lo que está sucediendo: javap es tu amigo, y también lo son un grupo de banderas de Scala que muestran lo que está pasando.

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

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

respondido por el Daniel C. Sobral 24.01.2012 - 23:04
20

De acuerdo con Juego de puntos de referencia para un sistema de un solo núcleo de 32 bits, Scala se encuentra en una mediana del 80% tan rápido como Java. El rendimiento es aproximadamente el mismo para una computadora Quad Core x64. Incluso uso de memoria y densidad de código son muy similares en mayoria de los casos. Diría que en base a estos análisis (más bien no científicos) que está en lo cierto al afirmar que Scala agrega cierta sobrecarga a Java. No parece que agregue toneladas de sobrecarga, por lo que sospecho que el diagnóstico de los artículos de orden superior que ocupan más espacio / tiempo es el más correcto.

    
respondido por el World Engineer 24.01.2012 - 20:21
18
  • El rendimiento de Scala es muy bueno si solo escribe código similar a Java / C en Scala. El compilador utilizará primitivas JVM para Int , Char , etc. cuando sea posible. Mientras que los bucles son igual de eficientes en Scala.
  • Tenga en cuenta que las expresiones lambda se compilan en instancias de subclases anónimas de las clases Function . Si pasa una lambda a map , la clase anónima debe crearse una instancia (y es posible que se deban pasar algunos locales), y luego cada iteración tiene una sobrecarga de llamada a función adicional (con algún parámetro que pasa) de las llamadas apply .
  • Muchas clases como scala.util.Random son solo envolturas alrededor de clases JRE equivalentes. La llamada a la función adicional es un poco inútil.
  • Cuidado con las implicaciones en el código crítico para el rendimiento. java.lang.Math.signum(x) es mucho más directo que x.signum() , que se convierte a RichInt y vuelve.
  • 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.
respondido por el Daniel Lubarov 24.01.2012 - 21:51
5
  • a) Según mi conocimiento limitado, debo señalar 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 observaciones largas, recomendaría no realizar una gran producción en la prueba de rendimiento (excepto que es exactamente lo que desea optimizar, pero ¿quién debería leer los 2 millones de valores?) Estás midiendo la impresión, lo que no es muy interesante. Sustituyendo 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 es un poco lenta (aunque debemos admitir que está mejorando todo el tiempo). Utilice while o funciones recursivas en su lugar. No en este ejemplo, donde está el bucle externo. Utilice la @ anotación-anotación, para probar la capacidad de corte.
  • d) La comparación con C / Assembler falla. No reescribir el código de 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 los errores de caché
    • 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.
respondido por el user unknown 24.01.2012 - 23:15
1

Echa un vistazo a mi publicación hace un año o más en el rendimiento de Scala.

enlace

Hice una comparación de una construcción scala común (usando Range) y una herramienta iterativa usando un contador de bucle de tipos long e int. Conseguí algunos resultados interesantes. También experimenté con el plugin scalacl para optimizar un montón de construcciones funcionales.

Tenga en cuenta que puede observar que mi experimento NO está probando el rendimiento de scala, sino el rendimiento de ciertas clases de bibliotecas como Range.

Tenga en cuenta también que las microbenchmarks siempre son un poco difíciles.

    
respondido por el John Lonergan 14.02.2013 - 00:54

Lea otras preguntas en las etiquetas