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?
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?
Antworten:
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.
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.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
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 @specialised
Annotation 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
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 :)
.par
in 2.9 ist.
.par
.
map
Methode nicht verwenden , ist verschwindend gering.
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.
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)
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 Option
Instanzen usw. Dadurch wird auch der Sammlungstyp beibehalten, sodass bigEnough
der Typ Array[File]
- ist. Array
Die collect
Implementierung 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 withFilter
Methode, die ich hier anstelle von verwendet habe, filter
behebt 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.
Präsentation von @higherkinded zu diesem Thema - Überlegungen zur Scala-Leistung, die einige Java / Scala-Vergleiche durchführen.
Werkzeuge:
Großartiger Blogpost:
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.
Java and Scala both compile down to JVM bytecode,
die mit einer so
zu der fraglichen Aussage kombinierten Aussage kombiniert wurde , die diffence isn't that big.
ich zeigen wollte, dass dies so
nur ein rhetorischer Trick und keine argumentative Schlussfolgerung ist.
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.