Ich tauche in die Welt der funktionalen Programmierung ein und lese überall, dass funktionale Sprachen für Multithreading- / Multicore-Programme besser sind. Ich verstehe, wie funktionale Sprachen eine Menge Dinge anders machen, wie Rekursion , Zufallszahlen usw. Aber ich kann nicht herausfinden, ob Multithreading in einer funktionalen Sprache schneller ist, weil es anders kompiliert ist oder weil ich es anders schreibe .
Ich habe zum Beispiel ein Programm in Java geschrieben, das ein bestimmtes Protokoll implementiert. In diesem Protokoll senden und empfangen die beiden Parteien Tausende von Nachrichten, verschlüsseln diese und senden sie immer wieder neu (und empfangen sie). Wie erwartet ist Multithreading der Schlüssel, wenn Sie in der Größenordnung von Tausenden arbeiten. In diesem Programm gibt es keine Sperren .
Wenn ich dasselbe Programm in Scala schreibe (das die JVM verwendet), ist diese Implementierung dann schneller? Wenn ja warum Liegt es am Schreibstil? Wenn es ist wegen des Schreibstiles, jetzt , dass Java Lambda - Ausdrücke enthält, kann nicht erreiche ich die gleichen Ergebnisse Java mit Lambda verwenden? Oder ist es schneller, weil Scala die Dinge anders kompiliert?
quelle
Antworten:
Der Grund, warum Leute sagen, dass funktionale Sprachen für die Parallelverarbeitung besser sind, liegt in der Tatsache, dass sie normalerweise einen veränderlichen Zustand vermeiden. Der veränderbare Zustand ist die "Wurzel allen Übels" im Kontext der Parallelverarbeitung; Sie machen es wirklich einfach, auf Rennbedingungen zu stoßen, wenn sie von gleichzeitigen Prozessen geteilt werden. Die Lösung für die Race-Bedingungen beinhaltet dann, wie Sie bereits erwähnt haben, Sperr- und Synchronisationsmechanismen, die zu einem Laufzeit-Overhead führen, da die Prozesse aufeinander warten, bis sie die gemeinsam genutzte Ressource nutzen, und eine größere Designkomplexität, wie dies bei all diesen Konzepten der Fall ist tief in solchen Anwendungen verschachtelt.
Wenn Sie einen veränderlichen Zustand vermeiden, werden keine Synchronisations- und Sperrmechanismen mehr benötigt. Da funktionale Sprachen in der Regel einen veränderlichen Status vermeiden, sind sie für die parallele Verarbeitung von Natur aus effizienter und effektiver - Sie haben nicht den Laufzeitaufwand für gemeinsam genutzte Ressourcen und nicht die zusätzliche Designkomplexität, die normalerweise folgt.
Dies ist jedoch alles nebensächlich. Wenn Ihre Java-Lösung auch einen veränderlichen Status (der speziell zwischen Threads geteilt wird) vermeidet, würde die Konvertierung in eine funktionale Sprache wie Scala oder Clojure keine Vorteile in Bezug auf die gleichzeitige Effizienz bringen, da die ursprüngliche Lösung bereits frei von dem durch verursachten Overhead ist die Verriegelungs- und Synchronisationsmechanismen.
TL; DR: Wenn eine Lösung in Scala bei der Parallelverarbeitung effizienter ist als eine in Java, liegt dies nicht an der Art und Weise, wie der Code in der JVM kompiliert oder ausgeführt wird, sondern daran, dass die Java-Lösung den veränderlichen Status zwischen Threads teilt. Dies kann entweder zu Rennbedingungen führen oder den Synchronisationsaufwand erhöhen, um diese zu vermeiden.
quelle
Art von beidem. Es ist schneller, weil es einfacher ist, Ihren Code so zu schreiben, dass er schneller kompiliert werden kann. Sie werden nicht unbedingt einen Geschwindigkeitsunterschied durch das Wechseln der Sprache erhalten, aber wenn Sie mit einer funktionalen Sprache begonnen hätten, hätten Sie das Multithreading wahrscheinlich mit viel weniger Programmieraufwand durchführen können . Entsprechend ist es für einen Programmierer viel einfacher, Threading-Fehler zu machen, die in einer imperativen Sprache Geschwindigkeit kosten, und es ist viel schwieriger, diese Fehler zu bemerken.
Der Grund dafür ist, dass zwingende Programmierer im Allgemeinen versuchen, den gesamten sperrenfreien Code mit Threads in eine möglichst kleine Box zu packen und ihn so schnell wie möglich wieder in ihre komfortable, wandelbare, synchrone Welt zurückzuführen. Die meisten Fehler, die Ihre Geschwindigkeit kosten, werden an dieser Grenzfläche gemacht. In einer funktionalen Programmiersprache müssen Sie sich nicht so viele Gedanken darüber machen, an dieser Grenze Fehler zu machen. Der Großteil Ihrer Telefonvorwahl befindet sich sozusagen "in der Box".
quelle
Funktionale Programmierung sorgt in der Regel nicht für schnellere Programme. Dies erleichtert die parallele und gleichzeitige Programmierung. Hierfür gibt es zwei Hauptschlüssel:
Ein hervorragendes Beispiel für Punkt 2 ist, dass wir in Haskell klar zwischen deterministischer Parallelität und nicht deterministischer Parallelität unterscheiden . Es gibt keine bessere Erklärung, als Simon Marlows hervorragendes Buch Parallel and Concurrent Programming in Haskell zu zitieren (Zitate stammen aus Kapitel 1 ):
Darüber hinaus erwähnt Marlow auch die Dimension des Determinismus :
In Haskell sind die Parallelitäts- und Nebenläufigkeitsfunktionen auf diese Konzepte ausgelegt. Insbesondere, welche anderen Sprachen als ein Feature-Set zusammengefasst sind, teilt sich Haskell in zwei:
Wenn Sie nur versuchen, eine reine, deterministische Berechnung zu beschleunigen, macht die deterministische Parallelität die Sache oft viel einfacher. Oft macht man einfach so etwas:
Ich habe das tatsächlich mit einem meiner Spielzeugprojektprogramme vor ein paar Wochen gemacht . Es war trivial, das Programm zu parallelisieren. Das Wichtigste, was ich tun musste, war tatsächlich, einen Code hinzuzufügen, der besagt, dass die Elemente dieser Liste parallel berechnet werden (Zeile 90), und ich bekam einen nahezu linearen Durchsatzschub einige meiner teureren Testfälle.
Ist mein Programm schneller als mit herkömmlichen sperrenbasierten Multithreading-Dienstprogrammen? Das bezweifle ich sehr. Das Ordentliche in meinem Fall war, so viel aus so wenig Geld herauszuholen - mein Code ist wahrscheinlich sehr suboptimal, aber weil es so einfach zu parallelisieren ist, habe ich mit viel weniger Aufwand eine große Beschleunigung erzielt, als ihn richtig zu profilieren und zu optimieren. und keine Gefahr von Rennbedingungen. Und das ist, so würde ich behaupten, die Hauptmethode, mit der Sie mit funktionaler Programmierung "schnellere" Programme schreiben können.
quelle
In Haskell ist eine Änderung buchstäblich unmöglich, ohne dass spezielle veränderbare Variablen über eine Änderungsbibliothek abgerufen werden. Stattdessen erstellen Funktionen die Variablen, die sie benötigen, gleichzeitig mit ihren Werten (die träge berechnet werden), und der Müll wird gesammelt, wenn er nicht mehr benötigt wird.
Selbst wenn Sie Änderungsvariablen benötigen, können Sie diese in der Regel sparsam und zusammen mit den nicht änderbaren Variablen verwenden. (Eine andere nette Sache in haskell ist STM, das Sperren durch atomare Operationen ersetzt, aber ich bin nicht sicher, ob dies nur für die funktionale Programmierung ist oder nicht.) Normalerweise muss nur ein Teil des Programms parallel gemacht werden, um die Dinge zu verbessern leistungsmäßig.
Dies macht die Parallelität in Haskell oftmals einfach, und es werden Anstrengungen unternommen, um sie automatisch zu machen. Bei einfachem Code können Parallelität und Logik sogar getrennt werden.
Aufgrund der Tatsache, dass die Auswertungsreihenfolge in Haskell keine Rolle spielt, erstellt der Compiler lediglich die auszuwertenden Warteschlangensachen und sendet sie an die verfügbaren Kerne, sodass Sie eine Reihe von "Threads" erstellen können, die dies nicht tun tatsächlich werden fäden erst nötig. Die Bewertungsreihenfolge, die keine Rolle spielt, ist charakteristisch für die Reinheit, die normalerweise eine funktionale Programmierung erfordert.
Weitere Lektüre
Parallelität in Haskell (HaskellWiki) Parallele und gleichzeitige Programmierung in Haskell
in "Real-World Haskell"
von Simon Marlow
quelle
grep java this_post
.grep scala this_post
undgrep jvm this_post
zurück keine Ergebnisse :)