Scala vs Java, Leistung und Gedächtnis? [geschlossen]

160

Ich möchte mich gerne mit Scala befassen und habe eine grundlegende Frage, auf die ich anscheinend keine Antwort finden kann: Gibt es im Allgemeinen einen Unterschied in der Leistung und der Speichernutzung zwischen Scala und Java?

John Smith
quelle
3
Ich habe Behauptungen gehört, dass die Leistung sehr nahe sein kann. Ich vermute, es hängt stark davon ab, was Sie tun. (wie es für Java vs C ist)
Peter Lawrey
Die Antwort auf diese Art von Fragen lautet "es kommt darauf an" - für praktisch jeden Vergleich von System X mit System Y. Außerdem ist dies ein Duplikat von stackoverflow.com/questions/2479819/…
James Moore

Antworten:

261

Scala macht es sehr einfach, enorme Mengen an Speicher zu verwenden, ohne es zu merken. Dies ist normalerweise sehr mächtig, kann aber gelegentlich ärgerlich sein. Angenommen, Sie haben ein Array von Zeichenfolgen (aufgerufen array) und eine Zuordnung von diesen Zeichenfolgen zu Dateien (aufgerufen mapping). Angenommen, Sie möchten alle Dateien abrufen, die sich in der Karte befinden und aus Zeichenfolgen mit einer Länge von mehr als zwei stammen. In Java könnten Sie

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

Wütend! Harte Arbeit. In Scala ist der kompakteste Weg, dasselbe zu tun:

val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)

Einfach! Wenn Sie jedoch nicht mit der Funktionsweise der Sammlungen vertraut sind, werden Sie möglicherweise nicht feststellen, dass auf diese Weise ein zusätzliches Zwischenarray (mit filter) und ein zusätzliches Objekt für jedes Element des Arrays (mit mapping.get, das zurückgegeben wird) erstellt wurde eine Option). Außerdem werden zwei Funktionsobjekte erstellt (eines für den Filter und eines für die flatMap). Dies ist jedoch selten ein großes Problem, da Funktionsobjekte klein sind.

Grundsätzlich ist die Speichernutzung auf primitiver Ebene dieselbe. Die Bibliotheken von Scala verfügen jedoch über viele leistungsstarke Methoden, mit denen Sie sehr einfach eine enorme Anzahl von (normalerweise kurzlebigen) Objekten erstellen können. Der Garbage Collector ist normalerweise ziemlich gut mit dieser Art von Müll, aber wenn Sie nicht wissen, welcher Speicher verwendet wird, werden Sie in Scala wahrscheinlich früher auf Probleme stoßen als in Java.

Beachten Sie, dass der Scala-Code für das Computersprachen-Benchmark-Spiel in einem eher Java-ähnlichen Stil geschrieben ist, um eine Java-ähnliche Leistung zu erzielen, und daher eine Java-ähnliche Speichernutzung aufweist. Sie können dies in Scala tun: Wenn Sie Ihren Code so schreiben, dass er wie leistungsstarker Java-Code aussieht, handelt es sich um leistungsstarken Scala-Code. ( Möglicherweise können Sie es in einem idiomatischeren Scala-Stil schreiben und trotzdem eine gute Leistung erzielen, dies hängt jedoch von den Besonderheiten ab.)

Ich sollte hinzufügen, dass mein Scala-Code pro Programmierzeit normalerweise schneller ist als mein Java-Code, da ich in Scala die mühsamen, nicht leistungskritischen Teile mit weniger Aufwand erledigen und mehr Aufmerksamkeit auf die Optimierung der Algorithmen und verwenden kann Code für die leistungskritischen Teile.

Rex Kerr
quelle
172
+1 für diesen letzten Absatz. Es ist ein wichtiger Punkt, der viel zu oft außer Betracht gelassen wird .
Kevin Wright
2
Ich dachte, dass Ansichten bei den von Ihnen erwähnten Problemen sehr hilfreich sein könnten. Oder stimmt das nicht speziell mit Arrays?
Nicolas Payette
1
@ Kevin Wright - "Es ist ein wichtiger Punkt, der viel zu oft außer Betracht gelassen wird" - Es ist etwas, das leicht zu sagen und schwer zu demonstrieren ist und uns etwas über Rex Kerrs Fähigkeiten erzählt, nicht über das, was andere weniger qualifizierte erreichen.
igouy
1
>> im idiomatischen Stil mit Leistung der Superlative << Im Benchmark-Spiel ist Platz für idiomatische Scala-Programme, die keine Leistung der "Superlative" erzielen.
igouy
2
@RexKerr - sucht Ihr Java-Beispiel den Zuordnungsschlüssel nicht zweimal für jeden möglichen String, während Ihr Scala-Beispiel dies nur einmal tut, nachdem die Strings ausgewählt wurden? Dh sie werden auf unterschiedliche Weise für unterschiedliche Datensätze optimiert?
Seth
103

Ich bin ein neuer Benutzer, daher kann ich Rex Kerrs Antwort oben keinen Kommentar hinzufügen (neue Benutzer können "antworten", aber nicht "kommentieren" ist übrigens eine sehr seltsame Regel).

Ich habe mich einfach angemeldet, um auf die Andeutung von Rex 'populärer Antwort oben zu antworten: "Puh, Java ist so ausführlich und so harte Arbeit." Während Sie natürlich präziseren Scala-Code schreiben können, ist das angegebene Java-Beispiel deutlich aufgebläht. Die meisten Java-Entwickler würden so etwas codieren:

List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
  if(s.length() > 2 && mapping.get(s) != null) {
    bigEnough.add(mapping.get(s));
  }
}

Und wenn wir so tun wollen, als würde Eclipse den größten Teil der eigentlichen Eingabe nicht für Sie erledigen und dass jedes gespeicherte Zeichen Sie wirklich zu einem besseren Programmierer macht, könnten Sie Folgendes codieren:

List b=new ArrayList();
for(String s:array)
  if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));

Jetzt habe ich nicht nur die Zeit gespart, die ich für die Eingabe vollständiger Variablennamen und geschweifter Klammern benötigt habe (so dass ich noch 5 Sekunden Zeit habe, um über tiefgreifende algorithmische Gedanken nachzudenken), sondern ich kann meinen Code auch in Verschleierungswettbewerben eingeben und möglicherweise zusätzliches Geld dafür verdienen die Ferien.

Nicht schlafend
quelle
7
Wieso bist du kein Mitglied des Clubs "Hip Language of the Month"? Schöne Kommentare. Ich habe es besonders genossen, den letzten Absatz zu lesen.
Stepanian
21
Hervorragend ausgedrückt! Ich werde müde von erfundenen Beispielen, bei denen auf aufgeblasenen Java-Code ein sorgfältig konstruiertes, knappes Beispiel für Scala (oder eine andere FP-Sprache) folgt und dann eine hastig gezogene Schlussfolgerung, dass Scala deswegen besser sein muss als Java. Wer hat jemals etwas Bedeutendes in Scala geschrieben? ;-) Und sag nicht Twitter ...
chrisjleu
2
Nun, die Lösung von Rex weist den Speicher für das Array vorab zu, wodurch der kompilierte Code schneller ausgeführt wird (da Sie bei Ihrem Ansatz die JVM Ihr Array regelmäßig neu zuweisen lassen, wenn es wächst). Obwohl mehr getippt wurde, könnte es in Bezug auf die Leistung ein Gewinner sein.
Ashalynd
5
während wir dabei sind, wird es in java8 sein:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl
2
Was Scala in gewisser Weise "besser" macht als Java, sind die erweiterten Typsystemfunktionen, die es einfacher machen, allgemeinere Muster als Typen (wie Monaden, Funktoren usw.) auszudrücken. Auf diese Weise können Sie Typen erstellen, die Ihnen aufgrund zu strenger Verträge nicht im Weg stehen, wie dies in Java häufig der Fall ist. Strenge Verträge, die nicht auf tatsächlichen Mustern im Code basieren, sind der Grund, warum Inversion of Responsibility-Muster erforderlich sind, um Ihren Code ordnungsgemäß zu testen (Dependence Injection kommt zuerst in den Sinn und die damit verbundene XML-Hölle). Das Addl. Prägnanz, die die Flexibilität mit sich bringt, ist nur ein Bonus.
Josiah
67

Schreiben Sie Ihre Scala wie Java, und Sie können erwarten, dass nahezu identischer Bytecode ausgegeben wird - mit nahezu identischen Metriken.

Schreiben Sie es "idiomatischer" mit unveränderlichen Objekten und Funktionen höherer Ordnung, und es wird etwas langsamer und etwas größer. Die einzige Ausnahme von dieser Faustregel besteht darin, dass bei Verwendung generischer Objekte, bei denen die Typparameter die @specialisedAnnotation verwenden, ein noch größerer Bytecode erstellt wird, der die Leistung von Java übertreffen kann, indem Boxing / Unboxing vermieden wird.

Erwähnenswert ist auch die Tatsache, dass mehr Speicher / weniger Geschwindigkeit ein unvermeidlicher Kompromiss beim Schreiben von Code ist, der parallel ausgeführt werden kann. Der idiomatische Scala-Code ist weitaus aussagekräftiger als der typische Java-Code und oft nur 4 Zeichen ( .par) von der vollständigen Parallelität entfernt.

Also wenn

  • Scala-Code dauert 1,25x länger als Java-Code in einem einzelnen Thread
  • Es kann leicht auf 4 Kerne aufgeteilt werden (jetzt sogar bei Laptops üblich)
  • für eine parallele Laufzeit von (1,24 / 4 =) 0,3125x das ursprüngliche Java

Würden Sie dann sagen, dass der Scala-Code jetzt vergleichsweise 25% langsamer oder 3x schneller ist?

Die richtige Antwort hängt davon ab, wie Sie "Leistung" definieren :)

Kevin Wright
quelle
4
Im Übrigen möchten Sie vielleicht erwähnen, dass dies .parin 2.9 ist.
Rex Kerr
26
>> Würden Sie dann sagen, dass der Scala-Code jetzt vergleichsweise 25% langsamer oder dreimal schneller ist? << Ich würde sagen, warum ist Ihr hypothetischer Vergleich nicht mit Multithread-Java-Code?
igouy
17
@igouy - Der Punkt ist, dass der hypothetische Code nicht existiert. Die zwingende Natur des "schnelleren" Java-Codes erschwert die Parallelisierung erheblich, so dass das Kosten-Nutzen-Verhältnis bedeutet, dass dies überhaupt nicht wahrscheinlich ist. Die idiomatische Scala hingegen, die weitaus aussagekräftiger ist, kann häufig gleichzeitig mit nur einer trivialen Änderung durchgeführt werden.
Kevin Wright
7
Das Vorhandensein gleichzeitiger Java-Programme bedeutet nicht, dass ein typisches Java-Programm leicht an die Parallelität angepasst werden kann. Wenn überhaupt, würde ich sagen, dass ein bestimmter Fork-Join-Stil in Java besonders selten ist und explizit codiert werden muss, während einfache Operationen wie das Ermitteln des minimalen enthaltenen Werts oder der Summe der Werte in einer Sammlung trivial parallel ausgeführt werden können in Scala durch einfaches Verwenden .par.
Kevin Wright
5
Nein, ich könnte nicht. Diese Art von Dingen ist ein grundlegender Baustein für viele Algorithmen, und es ist ein Beweis dafür, dass Sie in der Sprache und den Standardbibliotheken (denselben Standardbibliotheken, die alle Programme verwenden, nicht nur typischen) auf einem so niedrigen Niveau vorhanden sind. Sie sind schon näher dran, gleichzeitig zu sein, indem Sie einfach die Sprache auswählen. Beispielsweise ist die Zuordnung über eine Sammlung von Natur aus für die Parallelisierung geeignet, und die Anzahl der Scala-Programme, die diese mapMethode nicht verwenden , ist verschwindend gering.
Kevin Wright
31

Computersprachen-Benchmarks-Spiel:

Geschwindigkeitstest Java / Scala 1.71 / 2.25

Gedächtnistest Java / Scala 66.55 / 80.81

Diese Benchmarks besagen also, dass Java 24% schneller ist und Scala 21% mehr Speicher benötigt.

Alles in allem ist es keine große Sache und sollte in realen Apps, in denen die meiste Zeit von Datenbank und Netzwerk verbraucht wird, keine Rolle spielen.

Fazit: Wenn Scala Sie und Ihr Team (und die Leute, die das Projekt übernehmen, wenn Sie gehen) produktiver macht, sollten Sie sich dafür entscheiden.

Peter Knego
quelle
34
Code Größe Java / Scala 3.39 / 2.21
Hammar
22
Seien Sie vorsichtig mit Zahlen wie diesen, sie klingen schrecklich präzise, ​​während sie in Wirklichkeit fast nichts bedeuten. Es ist nicht so, dass Scala im Durchschnitt immer 24% schneller ist als Java usw.
Jesper
3
Von Afaik zitierte Zahlen weisen auf das Gegenteil hin: Java ist 24% schneller als Scala. Aber wie Sie sagen - es handelt sich um Mikrobenchmarks, die nicht mit dem übereinstimmen müssen, was in echten Apps geschieht. Und die unterschiedliche Art und Weise oder Problemlösung in verschiedenen Sprachen könnte letztendlich zu weniger vergleichbaren Programmen führen.
Benutzer unbekannt
9
"Wenn Scala dich und dein Team macht ..."
Fazit
Die Hilfeseite des Benchmark-Spiels enthält ein Beispiel für das Vergleichen von Programmgeschwindigkeit und -größe für Implementierungen in zwei Sprachen. Für Scala und Java die entsprechende Vergleich Web - Seite - shootout.alioth.debian.org/u64q/scala.php
igouy
20

Andere haben diese Frage in Bezug auf enge Schleifen beantwortet, obwohl es einen offensichtlichen Leistungsunterschied zwischen Rex Kerrs Beispielen zu geben scheint, die ich kommentiert habe.

Diese Antwort richtet sich wirklich an Personen, die möglicherweise die Notwendigkeit einer Optimierung im engen Regelkreis als Konstruktionsfehler untersuchen.

Ich bin relativ neu in Scala (etwa ein Jahr oder so) , aber das Gefühl es, so weit ist , dass es Ihnen erlaubt zu verschieben viele Aspekte der Gestaltung, Umsetzung und Ausführung relativ einfach (mit genügend Hintergrund Lesen und Experimentieren :)

Aufgeschobene Konstruktionsmerkmale:

Verzögerte Implementierungsfunktionen:

Verzögerte Ausführungsfunktionen: (Entschuldigung, keine Links)

  • Thread-sichere Lazy-Werte
  • Pass-by-Name
  • Monadisches Zeug

Diese Funktionen helfen mir, den Weg zu schnellen und engen Anwendungen zu beschreiten.


Die Beispiele von Rex Kerr unterscheiden sich darin, welche Aspekte der Ausführung verschoben werden. Im Java-Beispiel wird die Zuweisung von Speicher verschoben, bis die Größe berechnet ist, wobei das Scala-Beispiel die Mapping-Suche verzögert. Für mich scheinen sie völlig andere Algorithmen zu sein.

Folgendes ist meiner Meinung nach eher ein Äpfel-zu-Äpfel-Äquivalent für sein Java-Beispiel:

val bigEnough = array.collect({
    case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})

Keine zwischengeschalteten Sammlungen, keine OptionInstanzen usw. Dadurch wird auch der Sammlungstyp beibehalten, sodass bigEnoughder Typ Array[File]- ist. ArrayDie collectImplementierung wird wahrscheinlich etwas in der Art tun, wie der Java-Code von Herrn Kerr funktioniert.

Die oben aufgeführten verzögerten Designfunktionen würden es Scalas Sammlungs-API-Entwicklern auch ermöglichen, diese schnelle Array-spezifische Sammlungsimplementierung in zukünftigen Versionen zu implementieren, ohne die API zu beschädigen. Darauf beziehe ich mich, wenn ich den Weg zur Geschwindigkeit beschreite.

Ebenfalls:

val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)

Die withFilterMethode, die ich hier anstelle von verwendet habe, filterbehebt das Problem mit der Zwischenerfassung, aber es gibt immer noch das Problem mit der Optionsinstanz.


Ein Beispiel für die einfache Ausführungsgeschwindigkeit in Scala ist die Protokollierung.

In Java könnten wir so etwas schreiben wie:

if (logger.isDebugEnabled())
    logger.debug("trace");

In Scala ist dies nur:

logger.debug("trace")

weil der in Scala zu debuggende Nachrichtenparameter den Typ " => String" hat, den ich als eine parameterlose Funktion betrachte, die ausgeführt wird, wenn er ausgewertet wird, die in der Dokumentation jedoch als "Pass-by-Name" bezeichnet wird.

BEARBEITEN {Funktionen in Scala sind Objekte, daher gibt es hier ein zusätzliches Objekt. Für meine Arbeit lohnt es sich, das Gewicht eines trivialen Objekts auszuschließen, dass eine Protokollnachricht unnötig ausgewertet wird. }}

Dies macht den Code nicht schneller, aber es erhöht die Wahrscheinlichkeit, dass er schneller ist, und es ist weniger wahrscheinlich, dass wir die Erfahrung haben, den Code anderer Leute massenhaft durchzugehen und zu bereinigen.

Für mich ist dies ein konsequentes Thema innerhalb von Scala.


Harter Code kann nicht erfassen, warum Scala schneller ist, obwohl er ein wenig andeutet.

Ich bin der Meinung, dass dies eine Kombination aus der Wiederverwendung von Code und der Obergrenze der Codequalität in Scala ist.

In Java wird großartiger Code oft zu einem unverständlichen Chaos und ist daher in APIs mit Produktionsqualität nicht wirklich realisierbar, da die meisten Programmierer ihn nicht verwenden könnten.

Ich habe große Hoffnungen, dass Scala es den einsteins unter uns ermöglichen könnte, weitaus kompetentere APIs zu implementieren, die möglicherweise durch DSLs ausgedrückt werden. Die Kern-APIs in Scala sind bereits weit auf diesem Weg.

Seth
quelle
Ihre Protokollierung ist ein gutes Beispiel für die Leistungsprobleme von Scala: logger.debug ("trace") erstellt ein neues Objekt für die Funktion ohne Parameter.
jcsahnwaldt Reinstate Monica
In der Tat - wie wirkt sich das auf meinen zugehörigen Punkt aus?
Seth
Die vorgenannten Objekte können auch verwendet werden, um aus Gründen der Effizienz transparente IoC-Steuerstrukturen herzustellen. Ja, dasselbe Ergebnis ist theoretisch in Java möglich, aber es wäre etwas, das die Art und Weise, wie Code geschrieben wird, dramatisch beeinflusst / verschleiert - daher mein Argument, dass Scalas Talent, viele Elemente der Softwareentwicklung aufzuschieben, uns dabei hilft, schnelleren Code zu erreichen - eher wahrscheinlich in der Praxis schneller als geringfügig schneller.
Seth
Ok, ich habe das noch einmal gelesen und "einfache Ausführungsgeschwindigkeit" geschrieben - ich werde eine Notiz hinzufügen. Guter Punkt :)
Seth
3
Vorhersagbare if-Anweisung (grundsätzlich kostenlos auf einem superskalaren Prozessor) vs. Objektzuweisung + Müll. Der Java-Code ist offensichtlich schneller (beachten Sie, dass er nur die Bedingung auswertet, die Ausführung die Protokollanweisung nicht erreicht.) Als Antwort auf "Für meine Arbeit lohnt es sich, das Gewicht eines trivialen Objekts auszuschließen, dass eine Protokollnachricht unnötig ausgewertet wird . "
Eloff
10

Java und Scala werden beide bis zum JVM-Bytecode kompiliert, sodass der Unterschied nicht so groß ist. Der beste Vergleich, den Sie erhalten können, ist wahrscheinlich das Computersprachen-Benchmark-Spiel , das im Wesentlichen besagt, dass Java und Scala beide dieselbe Speichernutzung haben. Scala ist bei einigen der aufgeführten Benchmarks nur geringfügig langsamer als Java. Dies kann jedoch einfach daran liegen, dass die Implementierung der Programme unterschiedlich ist.

Wirklich, beide sind sich so nahe, dass es sich nicht lohnt, sich Sorgen zu machen. Die Produktivitätssteigerung, die Sie durch die Verwendung einer ausdrucksstärkeren Sprache wie Scala erzielen, ist weit mehr wert als ein minimaler (wenn überhaupt) Leistungseinbruch.

Ryeguy
quelle
7
Ich sehe hier einen logischen Irrtum: Beide Sprachen werden bis zum Bytecode kompiliert, aber ein erfahrener Programmierer und ein Neuling - ihr Code wird auch bis zum Bytecode kompiliert - aber nicht bis zum gleichen Bytecode, also die Schlussfolgerung, dass der Unterschied nicht so groß sein kann kann falsch sein. Tatsächlich konnte früher eine while-Schleife in scala viel, viel schneller sein als eine semantisch äquivalente for-Schleife (wenn ich mich richtig erinnere, ist sie heute viel besser). Und beide wurden natürlich zu Bytecode kompiliert.
Benutzer unbekannt
@user unknown - "Eine while-Schleife kann in Scala viel, viel schneller sein als eine semantisch äquivalente for-Schleife" - Beachten Sie, dass diese Scala-Benchmark-Spielprogramme mit while-Schleifen geschrieben werden.
igouy
@igouy: Ich habe nicht über die Ergebnisse dieser Mikrobank gesprochen, sondern über die Argumentation. Eine wahre Aussage, Java and Scala both compile down to JVM bytecode, die mit einer sozu der fraglichen Aussage kombinierten Aussage kombiniert wurde , die diffence isn't that big.ich zeigen wollte, dass dies sonur ein rhetorischer Trick und keine argumentative Schlussfolgerung ist.
Benutzer unbekannt
3
überraschend falsche Antwort mit überraschend hohen Stimmen.
Shabunc
4

Das Java-Beispiel ist wirklich keine Redewendung für typische Anwendungsprogramme. Ein solcher optimierter Code kann in einer Systembibliotheksmethode gefunden werden. Dann würde es jedoch ein Array des richtigen Typs verwenden, dh File [], und keine IndexOutOfBoundsException auslösen. (Unterschiedliche Filterbedingungen zum Zählen und Hinzufügen). Meine Version wäre (immer (!) Mit geschweiften Klammern, weil ich nicht gerne eine Stunde damit verbringe, einen Fehler zu suchen, der durch Speichern der 2 Sekunden zum Drücken einer einzelnen Taste in Eclipse eingeführt wurde):

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

Aber ich könnte Ihnen viele andere hässliche Java-Codebeispiele aus meinem aktuellen Projekt bringen. Ich habe versucht, den allgemeinen Kopier- und Änderungsstil der Codierung zu vermeiden, indem ich gemeinsame Strukturen und Verhaltensweisen herausgerechnet habe.

In meiner abstrakten DAO-Basisklasse habe ich eine abstrakte innere Klasse für den gemeinsamen Caching-Mechanismus. Für jeden konkreten Modellobjekttyp gibt es eine Unterklasse der abstrakten DAO-Basisklasse, in der die innere Klasse in eine Unterklasse unterteilt ist, um eine Implementierung für die Methode bereitzustellen, mit der das Geschäftsobjekt beim Laden aus der Datenbank erstellt wird. (Wir können kein ORM-Tool verwenden, da wir über eine proprietäre API auf ein anderes System zugreifen.)

Dieser Unterklassen- und Instanziierungscode ist in Java überhaupt nicht klar und in Scala sehr gut lesbar.

MickH
quelle