Prestazioni di Scala rispetto a Java


41

Prima di tutto vorrei chiarire che questa non è una domanda lingua-X-contro-lingua-Y per determinare quale sia la migliore.

Uso Java da molto tempo e intendo continuare ad usarlo. Parallelamente a questo, sto attualmente imparando Scala con grande interesse: a parte le cose minori che richiedono un po 'di tempo per abituarsi alla mia impressione è che posso davvero lavorare molto bene in questa lingua.

La mia domanda è: come si confronta il software scritto in Scala con il software scritto in Java in termini di velocità di esecuzione e consumo di memoria? Naturalmente, questa è una domanda difficile a cui rispondere in generale, ma mi aspetterei che costrutti di livello superiore come la corrispondenza dei modelli, le funzioni di ordine superiore, ecc., Introducano alcune spese generali.

Tuttavia, la mia attuale esperienza in Scala è limitata a piccoli esempi con 50 righe di codice e finora non ho eseguito alcun benchmark. Quindi, non ho dati reali.

Se si è scoperto che Scala ha un sovraccarico di Java, ha senso avere progetti Scala / Java misti, in cui si codificano le parti più complesse in Scala e le parti critiche per le prestazioni in Java? È una pratica comune?

MODIFICA 1

Ho eseguito un piccolo benchmark: crea un elenco di numeri interi, moltiplica ogni numero intero per due e lo inserisce in un nuovo elenco, stampa l'elenco risultante. Ho scritto un'implementazione Java (Java 6) e un'implementazione Scala (Scala 2.9). Ho eseguito entrambi su Eclipse Indigo con Ubuntu 10.04.

I risultati sono comparabili: 480 ms per Java e 493 ms per Scala (media di oltre 100 iterazioni). Ecco i frammenti che ho usato.

// 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))
}

Quindi, in questo caso sembra che il sovraccarico di Scala (usando range, map, lambda) sia davvero minimo, che non è lontano dalle informazioni fornite dall'ingegnere mondiale.

Forse ci sono altri costrutti Scala che dovrebbero essere usati con cura perché sono particolarmente pesanti da eseguire?

MODIFICA 2

Alcuni di voi hanno sottolineato che le stampe negli anelli interni occupano la maggior parte del tempo di esecuzione. Li ho rimossi e ho impostato la dimensione degli elenchi su 100000 anziché su 20000. La media risultante era 88 ms per Java e 49 ms per Scala.


5
Immagino che dal momento che Scala viene compilato con il codice byte JVM, le prestazioni potrebbero teoricamente essere equivalenti a Java in esecuzione con la stessa JVM a parità di tutte le altre cose. La differenza penso sia nel modo in cui il compilatore Scala crea il codice byte e se lo fa in modo efficiente.
maple_shaft

2
@maple_shaft: O forse c'è un sovraccarico nel tempo di compilazione di Scala?
FrustratedWithFormsDesigner

1
@Giorgio Non esiste alcuna distinzione di runtime tra oggetti Scala e oggetti Java, sono tutti oggetti JVM che sono definiti e si comportano in base al codice byte. Scala, ad esempio, come linguaggio ha il concetto di chiusure, ma quando vengono compilate vengono compilate in un numero di classi con codice byte. Teoricamente, potrei scrivere fisicamente il codice Java che potrebbe essere compilato nello stesso esatto codice byte e il comportamento di runtime sarebbe esattamente lo stesso.
maple_shaft

2
@maple_shaft: Questo è esattamente ciò a cui sto puntando: trovo il codice Scala sopra molto più conciso e leggibile del corrispondente codice Java. Mi stavo solo chiedendo se avrebbe senso scrivere parti di un progetto Scala in Java per motivi di performance, e quali sarebbero state quelle parti.
Giorgio,

2
Il runtime sarà in gran parte occupato dalle chiamate println. È necessario un test più intenso.
Kevin Cline,

Risposte:


39

C'è una cosa che puoi fare in modo conciso ed efficiente in Java che non puoi in Scala: le enumerazioni. Per tutto il resto, anche per costrutti lenti nella libreria di Scala, puoi ottenere versioni efficienti che funzionano in Scala.

Quindi, per la maggior parte, non è necessario aggiungere Java al codice. Anche per il codice che utilizza l'enumerazione in Java, c'è spesso una soluzione in Scala che è adeguata o buona - io metto l'eccezione su enumerazioni che hanno metodi extra e i cui valori int costanti sono usati.

Per quanto riguarda cosa fare attenzione, ecco alcune cose.

  • Se usi il modello di arricchimento della mia libreria, converti sempre in una classe. Per esempio:

    // 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)
  • Diffidare dei metodi di raccolta - poiché per la maggior parte sono polimorfici, JVM non li ottimizza. Non è necessario evitarli, ma prestare attenzione ad esso nelle sezioni critiche. Tieni presente che forin Scala è implementato tramite chiamate di metodo e classi anonime.

  • Se si utilizza una classe Java, come String, Arrayo le AnyValclassi che corrispondono alle primitive Java, preferire i metodi forniti da Java quando esistono alternative. Ad esempio, utilizzare lengthsu Stringe Arrayanziché size.

  • Evita l'uso incurante delle conversioni implicite, in quanto puoi ritrovarti a utilizzare le conversioni per errore invece che in base alla progettazione.

  • Estendi le classi anziché i tratti. Ad esempio, se stai estendendo Function1, estendi AbstractFunction1invece.

  • Uso -optimisee specializzazione per ottenere la maggior parte di Scala.

  • Comprendi cosa sta succedendo: javapè tuo amico, così come un gruppo di bandiere Scala che mostrano cosa sta succedendo.

  • I modi di dire di Scala sono progettati per migliorare la correttezza e rendere il codice più conciso e mantenibile. Non sono progettati per la velocità, quindi se è necessario utilizzare nullinvece che Optionin un percorso critico, farlo! C'è un motivo per cui Scala è multi-paradigma.

  • Ricorda che la vera misura delle prestazioni è l'esecuzione del codice. Vedi questa domanda per un esempio di cosa può accadere se ignori quella regola.


1
+1: Molte informazioni utili, anche su argomenti che devo ancora imparare, ma è utile aver letto qualche suggerimento prima di poterli esaminare.
Giorgio,

Perché il primo approccio usa la riflessione? Genera comunque una classe anonima, quindi perché non usarla al posto della riflessione?
Oleksandr.Bezhan,

@ Oleksandr.Bezhan La classe anonima è un concetto Java, non uno Scala. Genera un raffinamento di tipo. Non è possibile accedere dall'esterno a un metodo di classe anonimo che non sovrascrive la sua classe di base. Lo stesso non vale per i raffinamenti di tipo Scala, quindi l'unico modo per arrivare a quel metodo è attraverso la riflessione.
Daniel C. Sobral,

Sembra abbastanza terribile. In particolare: "Diffidare dei metodi di raccolta - poiché sono polimorfici per la maggior parte, JVM non li ottimizza. Non è necessario evitarli, ma prestare attenzione ad esso nelle sezioni critiche".
opaco

21

Secondo il Benchmarks Game per un sistema single core a 32 bit, Scala è all'80% veloce quanto Java. Le prestazioni sono approssimativamente le stesse per un computer Quad Core x64. Anche l'uso della memoria e la densità del codice sono molto simili nella maggior parte dei casi. Direi sulla base di queste (piuttosto poco scientifiche) analisi che tu abbia ragione nell'affermare che Scala aggiunge un certo sovraccarico a Java. Non sembra aggiungere tonnellate di spese generali, quindi sospetto che la diagnosi di articoli di ordine superiore che occupano più spazio / tempo sia la più corretta.


2
Per questa risposta ti preghiamo di usare il confronto diretto come suggerisce la pagina di aiuto ( shootout.alioth.debian.org/help.php#comparetwo )
igouy,

18
  • Le prestazioni di Scala sono molto decenti se si scrive semplicemente codice simile a Java / C in Scala. Il compilatore utilizzerà primitive JVM per Int, Charecc quando si può. Mentre i loop sono altrettanto efficienti in Scala.
  • Tieni presente che le espressioni lambda vengono compilate in istanze di sottoclassi anonime delle Functionclassi. Se si passa un lambda a map, è necessario creare un'istanza della classe anonima (e potrebbe essere necessario passare alcuni locali), quindi ogni iterazione ha un sovraccarico di chiamata di funzione extra (con il passaggio di alcuni parametri) dalle applychiamate.
  • Molte classi come scala.util.Randomsono solo involucri attorno a classi JRE equivalenti. La chiamata di funzione extra è leggermente dispendiosa.
  • Fai attenzione alle implicazioni nel codice critico per le prestazioni. java.lang.Math.signum(x)è molto più diretto di x.signum(), che converte in RichInte viceversa.
  • Il principale vantaggio prestazionale di Scala rispetto a Java è la specializzazione. Tieni presente che la specializzazione viene utilizzata con parsimonia nel codice della libreria.

5
  • a) Per mia conoscenza limitata, devo sottolineare che il codice nel metodo principale statico non può essere ottimizzato molto bene. È necessario spostare il codice critico in una posizione diversa.
  • b) In base a lunghe osservazioni, consiglierei di non fare grandi risultati nel test delle prestazioni (tranne che è esattamente ciò che ti piace ottimizzare, ma chi dovrebbe mai leggere 2 milioni di valori?). Stai misurando println, il che non è molto interessante. Sostituzione della stampa con max:
(1 to 20000).toList.map (_ * 2).max

riduce il tempo da 800 ms a 20 sul mio sistema.

  • c) La comprensione per essere nota è un po 'lenta (mentre dobbiamo ammettere che sta migliorando continuamente). Utilizzare invece le funzioni while o tailrecursive. Non in questo esempio, dove si trova il ciclo esterno. Usa l'annotazione @ tailrec per verificare la tairecursività.
  • d) Il confronto con C / Assembler fallisce. Ad esempio, non si riscrive il codice scala per architetture diverse. Altre importanti differenze rispetto alle situazioni storiche sono
    • Compilatore JIT, ottimizzazione al volo e forse in modo dinamico, a seconda dei dati di input
    • L'importanza dei mancati cache
    • L'importanza crescente dell'invocazione parallela. Scala ha oggi soluzioni per lavorare senza sovraccarico in parallelo. Questo non è possibile in Java, tranne per il fatto che fai molto più lavoro.

2
Ho rimosso println dal loop e in realtà il codice Scala è più veloce del codice Java.
Giorgio,

Il confronto con C e Assembler era inteso nel senso seguente: un linguaggio di livello superiore ha astrazioni più potenti ma potrebbe essere necessario utilizzare il linguaggio di livello inferiore per le prestazioni. Questo parallelo vale considerando Scala come il livello più alto e Java come il livello più basso linguaggio? Forse no, dal momento che Scala sembra offrire prestazioni simili a Java.
Giorgio,

Non penserei che sarebbe molto importante per Clojure o Scala, ma quando giocavo con jRuby e Jython avrei probabilmente scritto il codice più critico in termini di prestazioni in Java. Con quei due ho visto una significativa disparità, ma anni fa ormai ... potrebbe essere migliore.
Rig
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.