Performance von Scala im Vergleich zu Java

41

Zunächst möchte ich klarstellen, dass es sich nicht um eine Frage von Sprache X gegen Sprache Y handelt, um festzustellen, welche Frage besser ist.

Ich habe Java für eine lange Zeit benutzt und ich beabsichtige, es weiter zu benutzen. Parallel dazu lerne ich gerade Scala mit großem Interesse: Abgesehen von Kleinigkeiten, an die ich mich erst gewöhnen muss, kann ich in dieser Sprache wirklich sehr gut arbeiten.

Meine Frage ist: Wie verhält sich in Scala geschriebene Software in Bezug auf Ausführungsgeschwindigkeit und Speicherverbrauch zu in Java geschriebener Software? Natürlich ist diese Frage im Allgemeinen schwierig zu beantworten, aber ich würde erwarten, dass Konstrukte höherer Ebenen wie Mustervergleiche, Funktionen höherer Ordnung usw. einen gewissen Overhead verursachen.

Meine derzeitigen Erfahrungen mit Scala beschränken sich jedoch auf kleine Beispiele mit weniger als 50 Codezeilen, und ich habe bisher keine Benchmarks durchgeführt. Ich habe also keine wirklichen Daten.

Wenn es stellte sich heraus , dass Scala hat einige Kopf WRT Java, macht es Sinn gemischte Scala / Java - Projekte zu haben , machen, wo man Codes die komplexeren Teile in Scala und die erfolgskritischen Teile in Java? Ist das eine gängige Praxis?

EDIT 1

Ich habe einen kleinen Benchmark durchgeführt: Eine Liste mit ganzen Zahlen erstellen, jede ganze Zahl mit zwei multiplizieren und in eine neue Liste einfügen. Die resultierende Liste ausdrucken. Ich habe eine Java-Implementierung (Java 6) und eine Scala-Implementierung (Scala 2.9) geschrieben. Ich habe beide auf Eclipse Indigo unter Ubuntu 10.04 ausgeführt.

Die Ergebnisse sind vergleichbar: 480 ms für Java und 493 ms für Scala (gemittelt über 100 Iterationen). Hier sind die Schnipsel, die ich verwendet habe.

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

In diesem Fall scheint der Scala-Overhead (mit Bereich, Karte, Lambda) wirklich minimal zu sein, was nicht weit von den Informationen von World Engineer entfernt ist.

Vielleicht gibt es andere Scala-Konstrukte, die mit Vorsicht verwendet werden sollten, weil sie besonders schwer auszuführen sind?

BEARBEITEN 2

Einige von Ihnen haben darauf hingewiesen, dass die Ausdrucke in den inneren Schleifen die meiste Ausführungszeit in Anspruch nehmen. Ich habe sie entfernt und die Größe der Listen auf 100000 anstatt auf 20000 eingestellt. Der resultierende Durchschnitt betrug 88 ms für Java und 49 ms für Scala.

Giorgio
quelle
5
Ich stelle mir vor, dass, da Scala mit JVM-Bytecode kompiliert, die Leistung theoretisch mit Java vergleichbar sein könnte, das unter derselben JVM ausgeführt wird, wobei alle anderen Dinge gleich sind. Ich denke, der Unterschied besteht darin, wie der Scala-Compiler den Byte-Code erstellt und ob dies effizient ist.
maple_shaft
2
@maple_shaft: Oder gibt es einen Overhead in der Scala-Kompilierungszeit?
FrustratedWithFormsDesigner
1
@Giorgio Es gibt keine Laufzeitunterscheidung zwischen Scala-Objekten und Java-Objekten, sondern alle JVM-Objekte, die gemäß dem Bytecode definiert sind und sich verhalten. Scala zum Beispiel hat als Sprache das Konzept von Closures, aber wenn diese kompiliert werden, werden sie zu einer Reihe von Klassen mit Bytecode kompiliert. Theoretisch könnte ich Java-Code physisch schreiben, der mit genau demselben Bytecode kompiliert werden könnte, und das Laufzeitverhalten wäre genau dasselbe.
maple_shaft
2
@maple_shaft: Genau das ist mein Ziel: Ich finde den obigen Scala-Code viel prägnanter und lesbarer als den entsprechenden Java-Code. Ich habe mich nur gefragt, ob es aus Performancegründen sinnvoll wäre, Teile eines Scala-Projekts in Java zu schreiben und welche Teile das sein würden.
Giorgio
2
Die Laufzeit wird weitgehend von den println-Aufrufen belegt. Sie benötigen einen rechenintensiveren Test.
Kevin Cline

Antworten:

39

Es gibt eine Sache, die Sie in Java präzise und effizient ausführen können, die Sie in Scala nicht können: Aufzählungen. Für alles andere, auch für Konstrukte, die in der Bibliothek von Scala langsam sind, können Sie effiziente Versionen in Scala erstellen.

Daher müssen Sie Ihrem Code zum größten Teil kein Java hinzufügen. Selbst für Code, der in Java eine Aufzählung verwendet, gibt es in Scala häufig eine adäquate oder gute Lösung - ich setze die Ausnahme auf Aufzählungen, die über zusätzliche Methoden verfügen und deren int-konstante Werte verwendet werden.

Hier sind einige Dinge, auf die Sie achten sollten.

  • Wenn Sie das Muster Meine Bibliothek anreichern verwenden, konvertieren Sie immer in eine Klasse. Zum Beispiel:

    // 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)
    
  • Seien Sie vorsichtig mit Erfassungsmethoden - da diese zum größten Teil polymorph sind, optimiert JVM sie nicht. Sie brauchen sie nicht zu meiden, aber achten Sie auf kritische Abschnitte. Beachten Sie, dass forin Scala Methodenaufrufe und anonyme Klassen implementiert werden.

  • Bei Verwendung einer Java - Klasse, wie String, Arrayoder AnyValKlassen , die entsprechen Java Primitive, bevorzugen die Methoden von Java zur Verfügung gestellt , wenn es Alternativen gibt. Verwenden Sie beispielsweise lengthon Stringund Arrayanstelle von size.

  • Vermeiden Sie die unachtsame Verwendung impliziter Conversions, da Sie möglicherweise versehentlich Conversions anstelle von beabsichtigten Conversions verwenden.

  • Erweitern Sie Klassen anstelle von Merkmalen. Wenn Sie beispielsweise erweitern Function1, erweitern Sie AbstractFunction1stattdessen.

  • Verwendung -optimiseund Spezialisierung, um den größten Teil von Scala zu erhalten.

  • Verstehe, was passiert: javapIst dein Freund, und so sind auch ein paar Scala-Flaggen, die zeigen, was los ist.

  • Scala-Redewendungen sollen die Korrektheit verbessern und den Code übersichtlicher und wartbarer machen. Sie sind nicht auf Geschwindigkeit ausgelegt, so dass , wenn Sie verwenden müssen , nullstatt Optionin einem kritischen Pfad, tun! Es gibt einen Grund, warum Scala ein Multiparadigma ist.

  • Denken Sie daran, dass das wahre Maß für die Leistung die Ausführung von Code ist. In dieser Frage finden Sie ein Beispiel dafür, was passieren kann, wenn Sie diese Regel ignorieren.

Daniel C. Sobral
quelle
1
+1: Viele nützliche Informationen, auch zu Themen, die ich noch lernen muss, aber es ist nützlich, einen Hinweis gelesen zu haben, bevor ich sie mir anschaue.
Giorgio
Warum nutzt der erste Ansatz die Reflexion? Es wird trotzdem eine anonyme Klasse generiert. Warum also nicht statt Reflektion verwenden?
Oleksandr.Bezhan
@ Oleksandr.Bezhan Anonyme Klasse ist ein Java-Konzept, kein Scala-Konzept. Es wird eine Typverfeinerung generiert. Auf eine anonyme Klassenmethode, die ihre Basisklasse nicht überschreibt, kann von außen nicht zugegriffen werden. Dasselbe gilt nicht für die Typverfeinerungen von Scala. Der einzige Weg, um zu dieser Methode zu gelangen, ist die Reflexion.
Daniel C. Sobral
Das klingt ziemlich schrecklich. Insbesondere: "Seien Sie vorsichtig bei Erfassungsmethoden - da diese größtenteils polymorph sind, optimiert JVM sie nicht. Sie müssen sie nicht vermeiden, sondern auf kritische Abschnitte achten."
Matt
21

Nach dem Benchmarks-Spiel für ein 32-Bit-System mit einem Kern ist Scala im Median 80% so schnell wie Java. Die Leistung ist für einen Quad Core x64-Computer ungefähr gleich. Sogar die Speichernutzung und die Codedichte sind in den meisten Fällen sehr ähnlich. Ich würde auf der Grundlage dieser (eher unwissenschaftlichen) Analysen sagen, dass Sie zu Recht behaupten, dass Scala Java etwas Aufwand hinzufügt. Es scheint nicht viel Overhead hinzuzufügen, daher würde ich vermuten, dass die Diagnose von Artikeln höherer Ordnung, die mehr Platz / Zeit beanspruchen, die richtigste ist.

Weltingenieur
quelle
2
Für diese Antwort nutzen Sie bitte nur den direkten Vergleich , wie die Hilfeseite schlägt vor ( shootout.alioth.debian.org/help.php#comparetwo )
igouy
18
  • Die Leistung von Scala ist sehr anständig, wenn Sie nur Java / C-ähnlichen Code in Scala schreiben. Der Compiler verwenden JVM Primitiven für Int, Charusw. , wenn er kann. While-Schleifen sind in Scala genauso effizient.
  • Beachten Sie, dass Lambda-Ausdrücke zu Instanzen anonymer Unterklassen der FunctionKlassen kompiliert werden. Wenn Sie ein Lambda an übergeben map, muss die anonyme Klasse instanziiert werden (und einige lokale Klassen müssen möglicherweise übergeben werden), und dann hat jede Iteration zusätzlichen Funktionsaufruf-Overhead (mit einigen Parameterübergaben) von den applyAufrufen.
  • Viele Klassen wie scala.util.Randomsind nur Wrapper um äquivalente JRE-Klassen. Der zusätzliche Funktionsaufruf ist etwas verschwenderisch.
  • Achten Sie auf Auswirkungen auf leistungskritischen Code. java.lang.Math.signum(x)ist viel direkter als x.signum(), was zu RichIntund zurück konvertiert .
  • Der wichtigste Leistungsvorteil von Scala gegenüber Java ist die Spezialisierung. Beachten Sie, dass die Spezialisierung im Bibliothekscode nur sparsam verwendet wird.
Daniel Lubarov
quelle
5
  • a) Aufgrund meines begrenzten Wissens muss ich bemerken, dass Code in der statischen Hauptmethode nicht sehr gut optimiert werden kann. Sie sollten den kritischen Code an einen anderen Ort verschieben.
  • b) Aus langwierigen Beobachtungen heraus würde ich empfehlen, beim Leistungstest keine großen Ausgaben zu machen (außer es ist genau das, was Sie optimieren möchten, aber wer sollte jemals 2 Millionen Werte lesen?). Sie messen den Druck, was nicht sehr interessant ist. Ersetzen des Ausdrucks durch max:
(1 to 20000).toList.map (_ * 2).max

reduziert die Zeit von 800 ms auf 20 auf meinem System.

  • c) Es ist bekannt, dass das For-Comprehension etwas langsam ist (während wir zugeben müssen, dass es immer besser wird). Verwenden Sie stattdessen while- oder tailrecursive-Funktionen. Nicht in diesem Beispiel, in dem es sich um die äußere Schleife handelt. Verwenden Sie die @ tailrec-Annotation, um die Kursivität zu testen.
  • d) Der Vergleich mit C / Assembler schlägt fehl. Sie schreiben beispielsweise keinen Scala-Code für verschiedene Architekturen neu. Andere wichtige Unterschiede zu historischen Situationen sind
    • JIT-Compiler, der im laufenden Betrieb und möglicherweise dynamisch optimiert, abhängig von den Eingabedaten
    • Die Wichtigkeit von Cache-Fehlern
    • Die zunehmende Bedeutung der parallelen Anrufung. Scala bietet heute Lösungen, mit denen Sie ohne großen Aufwand parallel arbeiten können. Das ist in Java nicht möglich, außer Sie erledigen viel mehr Arbeit.
Benutzer unbekannt
quelle
2
Ich habe den println aus der Schleife entfernt und tatsächlich ist der Scala-Code schneller als der Java-Code.
Giorgio
Der Vergleich mit C und Assembler war in folgendem Sinne gemeint: Eine höhere Sprache verfügt über leistungsfähigere Abstraktionen, möglicherweise müssen Sie jedoch die niedrigere Sprache für die Leistung verwenden. Gilt diese Parallele, wenn man Scala als höhere und Java als niedrigere Sprache betrachtet? Möglicherweise nicht, da Scala eine ähnliche Leistung wie Java zu liefern scheint.
Giorgio
Ich hätte nicht gedacht, dass es für Clojure oder Scala wichtig wäre, aber als ich mit jRuby und Jython herumgespielt habe, hätte ich wahrscheinlich den leistungskritischeren Code in Java geschrieben. Mit diesen beiden sah ich eine signifikante Ungleichheit, aber das war vor Jahren ... könnte besser sein.
Rig