Warum sind Prozesse in Erlang technisch effizienter als OS-Threads?


170

Erlangs Eigenschaften

Aus der Erlang-Programmierung (2009):

Die Erlang-Parallelität ist schnell und skalierbar. Die Prozesse sind insofern leichtgewichtig, als die virtuelle Erlang-Maschine nicht für jeden erstellten Prozess einen Betriebssystem-Thread erstellt. Sie werden in der VM unabhängig vom zugrunde liegenden Betriebssystem erstellt, geplant und verarbeitet. Infolgedessen liegt die Prozesserstellungszeit in der Größenordnung von Mikrosekunden und unabhängig von der Anzahl der gleichzeitig vorhandenen Prozesse. Vergleichen Sie dies mit Java und C #, wo für jeden Prozess ein zugrunde liegender Betriebssystem-Thread erstellt wird: Sie erhalten einige sehr wettbewerbsfähige Vergleiche, wobei Erlang beide Sprachen deutlich übertrifft.

Aus der Parallelitätsorientierten Programmierung in Erlang (pdf) (Folien) (2003):

Wir stellen fest, dass die für die Erstellung eines Erlang-Prozesses benötigte Zeit konstant 1µs bis zu 2.500 Prozessen beträgt. danach steigt sie für bis zu 30.000 Prozesse auf etwa 3 us an. Die Leistung von Java und C # ist oben in der Abbildung dargestellt. Für eine kleine Anzahl von Prozessen dauert es ungefähr 300 us, um einen Prozess zu erstellen. Es ist unmöglich, mehr als zweitausend Prozesse zu erstellen.

Wir sehen, dass für bis zu 30.000 Prozesse die Zeit zum Senden einer Nachricht zwischen zwei Erlang-Prozessen etwa 0,8 µs beträgt. Für C # dauert es ungefähr 50µs pro Nachricht, bis zur maximalen Anzahl von Prozessen (die ungefähr 1800 Prozesse waren). Java war noch schlimmer, für bis zu 100 Prozesse dauerte es ungefähr 50 us pro Nachricht, danach stieg es schnell auf 10 ms pro Nachricht an, wenn es ungefähr 1000 Java-Prozesse gab.

Meine Gedanken

Ich verstehe technisch nicht ganz, warum Erlang-Prozesse beim Laichen neuer Prozesse so viel effizienter sind und viel weniger Speicherbedarf pro Prozess haben. Sowohl das Betriebssystem als auch die Erlang-VM müssen die Planung und den Kontextwechsel durchführen und die Werte in den Registern usw. verfolgen.

Warum werden Betriebssystem-Threads nicht wie Prozesse in Erlang implementiert? Müssen sie etwas mehr unterstützen? Und warum benötigen sie einen größeren Speicherbedarf? Und warum haben sie langsameres Laichen und Kommunikation?

Warum sind Prozesse in Erlang technisch gesehen effizienter als OS-Threads, wenn es um das Laichen und die Kommunikation geht? Und warum können Threads im Betriebssystem nicht auf dieselbe effiziente Weise implementiert und verwaltet werden? Und warum haben Betriebssystem-Threads einen größeren Speicherbedarf sowie ein langsameres Laichen und eine langsamere Kommunikation?

Mehr lesen


1
Bevor Sie versuchen, den Grund zu verstehen, warum eine Hypothese wahr ist, müssen Sie feststellen, ob die Hypothese wahr ist - z. B. gestützt auf die Beweise. Haben Sie Referenzen für jede like-for-like - Vergleiche zeigen , dass ein Erlang Prozess tatsächlich ist effizienter als (sagen wir) einen Java - Thread auf einem up-to-date JVM? Oder eine C-App, die OS-Prozess- und Thread-Unterstützung direkt verwendet? (Letzteres scheint mir sehr, sehr unwahrscheinlich. Ersteres nur etwas wahrscheinlich.) Ich meine, mit einer ausreichend begrenzten Umgebung (Franciscos Punkt) mag es wahr sein, aber ich möchte die Zahlen sehen.
TJ Crowder

1
@Donal: Wie bei so vielen anderen absoluten Aussagen. :-)
TJ Crowder

1
@ Jonas: Danke, aber ich bin bis zum Datum (1998-11-02) und JVM-Version (1.1.6) gekommen und habe aufgehört. Die JVM von Sun hat sich in den letzten 11,5 Jahren erheblich verbessert (und vermutlich auch Erlangs Dolmetscher), insbesondere im Bereich Threading. (Um ganz klar zu sein, ich sage nicht, dass die Hypothese nicht wahr ist [und Francisco und Donal haben darauf hingewiesen, warum Erland dort möglicherweise etwas tun kann]; ich sage, dass es nicht zum Nennwert genommen werden sollte ohne überprüft zu werden.)
TJ Crowder

1
@ Jonas: "... aber ich denke du kannst es in Erlang machen ..." Es ist dieser "Vermutung" Teil, Alter. :-) Sie vermuten, dass Erlangs Prozessumschaltung über die Tausenden hinausgeht. Sie vermuten, dass dies besser ist als Java- oder OS-Threads. Vermutung und Softwareentwicklung sind keine gute Kombination. :-) Aber ich denke, ich habe meinen Standpunkt klar gemacht.
TJ Crowder

17
@TJ Crowder: Installieren erlang und läuft erl +P 1000100 +hms 100und als Typ {_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).und als Wartezeit etwa drei Minuten für Ergebnis. Das ist so einfach. Es dauert 140us pro Prozess und 1 GB RAM auf meinem Laptop. Aber es ist direkt von der Shell, es sollte besser aus kompiliertem Code sein.
Hynek-Pichi-Vychodil

Antworten:


113

Es gibt mehrere Faktoren, die dazu beitragen:

  1. Erlang-Prozesse sind keine Betriebssystemprozesse. Sie werden von der Erlang-VM mithilfe eines einfachen kooperativen Threading-Modells implementiert (präventiv auf Erlang-Ebene, jedoch unter der Kontrolle einer kooperativ geplanten Laufzeit). Dies bedeutet, dass das Wechseln des Kontexts viel billiger ist, da nur an bekannten, kontrollierten Punkten gewechselt wird und daher nicht der gesamte CPU-Status (Normal-, SSE- und FPU-Register, Adressraumzuordnung usw.) gespeichert werden muss.
  2. Erlang-Prozesse verwenden dynamisch zugewiesene Stapel, die sehr klein beginnen und nach Bedarf wachsen. Dies ermöglicht das Laichen von vielen Tausenden - sogar Millionen - von Erlang-Prozessen, ohne den gesamten verfügbaren RAM zu verbrauchen.
  3. Erlang war früher Single-Threaded, was bedeutete, dass es nicht erforderlich war, die Thread-Sicherheit zwischen Prozessen zu gewährleisten. Es unterstützt jetzt SMP, aber die Interaktion zwischen Erlang-Prozessen auf demselben Scheduler / Kern ist immer noch sehr einfach (es gibt separate Ausführungswarteschlangen pro Kern).

6
Zu Ihrem zweiten Punkt: Und wenn der Prozess noch nicht ausgeführt wurde, gibt es keinen Grund, einen Stapel dafür zuzuweisen. Zusätzlich: Sie können mehrere Streiche spielen, indem Sie mit dem GC eines Prozesses herumspielen, sodass er niemals Speicher sammelt. Aber das ist fortgeschritten und etwas gefährlich :)
Ich gebe CRAP ANTWORTEN

3
Zu Ihrem dritten Punkt: Erlang erzwingt unveränderliche Daten, sodass die Einführung von SMP die Thread-Sicherheit nicht beeinträchtigen sollte.
Nilskp

@ nilskp, das stimmt, erlang ist auch eine funktionale Programmiersprache. Es gibt also keine "variablen" Daten. Dies führt zu Thread-Sicherheit.
Liuyang1

6
@nilskp: (RE: Sie kommentieren Punkt 3…) Obwohl die Sprache selbst ein unveränderliches Typsystem hat, ist die zugrunde liegende Implementierung - Nachrichtenübergabe, Scheduler usw. - eine ganz andere Geschichte. Die korrekte und effiziente SMP-Unterstützung erfolgte nicht nur per Knopfdruck.
Marcelo Cantos

@rvirding: Danke für den klarstellenden Nachtrag. Ich habe mir erlaubt, Ihre Punkte in den Körper meiner Antwort zu integrieren.
Marcelo Cantos

73

Nach einigen weiteren Recherchen fand ich eine Präsentation von Joe Armstrong.

Aus Erlang - Software für eine gleichzeitige Welt (Präsentation) (um 13 min):

[Erlang] ist eine gleichzeitige Sprache - damit meine ich, dass Threads Teil der Programmiersprache sind und nicht zum Betriebssystem gehören. Das ist wirklich das, was mit Programmiersprachen wie Java und C ++ falsch ist. Die Threads sind nicht in der Programmiersprache, Threads sind etwas im Betriebssystem - und sie erben alle Probleme, die sie im Betriebssystem haben. Eines der Probleme ist die Granularität des Speicherverwaltungssystems. Die Speicherverwaltung im Betriebssystem schützt ganze Speicherseiten, sodass die kleinste Größe eines Threads die kleinste Größe einer Seite ist. Das ist eigentlich zu groß.

Wenn Sie Ihrem Computer mehr Speicher hinzufügen - Sie haben die gleiche Anzahl von Bits, die den Speicher schützen, sodass die Granularität der Seitentabellen steigt -, verwenden Sie beispielsweise 64 KB für einen Prozess, von dem Sie wissen, dass er in einigen hundert Bytes ausgeführt wird.

Ich denke, es beantwortet, wenn nicht alle, zumindest einige meiner Fragen



2
Der Speicherschutz auf Stapeln ist nicht ohne Grund vorhanden. Schützt Erlang nicht die Stapel verschiedener Ausführungskontexte über die MMU des Prozessors? (Und nur auf das Beste hoffen?) Was ist, wenn ein Thread mehr als seinen winzigen Stapel verwendet? (Werden alle Stapelzuordnungen überprüft, um festzustellen, ob ein größerer Stapel benötigt wird? Ist der Stapel beweglich?)
Thanatos

2
@Thanatos: Erlang erlaubt Programmen nicht, auf Speicher zuzugreifen oder mit dem Stapel herumzuspielen. Alle Zuordnungen müssen die verwaltete Laufzeit durchlaufen, sowohl Heap als auch Stack. Mit anderen Worten: Hardwareschutz ist nutzlos, weil er vor Dingen schützt, die sowieso nicht passieren können. Die Sprache ist zeigersicher, stapelsicher, speichersicher und typsicher. Ein Prozess kann nicht mehr als seinen "winzigen Stapel" verwenden, da der Stapel nach Bedarf wächst. Sie können sich das Gegenteil von winzig vorstellen: unendlich groß. (Aber träge zugeteilt.)
Jörg W Mittag

4
Sie sollten sich das Singularity-Betriebssystem von Microsoft Research ansehen. In Singularity werden alle Code-, Kernel-, Gerätetreiber, Bibliotheken und Benutzerprogramme in Ring 0 mit vollständigen Kernel-Berechtigungen ausgeführt. Alle Code-, Kernel-, Gerätetreiber, Bibliotheken und Benutzerprogramme werden in einem einzigen flachen physischen Adressraum ohne jeglichen Speicherschutz ausgeführt. Das Team stellte fest, dass die Garantien, die die Sprache gibt, viel stärker sind als die Garantien, die die MMU geben kann, und dass die Verwendung der MMU gleichzeitig bis zu 30% (!!!) an Leistung kostet. Warum also die MMU verwenden, wenn Ihre Sprache dies ohnehin schon tut?
Jörg W Mittag

1
Das OS / 400-Betriebssystem funktioniert genauso. Es gibt nur einen einzigen flachen Adressraum für alle Programme. Und die meisten der heute tatsächlich verwendeten Sprachen haben dieselben Sicherheitseigenschaften (ECMAScript, Java, C♯, VB.NET, PHP, Perl, Python, Ruby, Clojure, Scala, Kotlin, Groovy, Ceylon, F♯, OCaml, die "Objective" Teil von "Objective-C", der "++" Teil von "C ++"). Ohne Legacy-C-Code und Legacy-Funktionen von C ++ und Objective-C würden wir nicht einmal mehr virtuellen Speicher benötigen.
Jörg W Mittag

47

Ich habe Coroutinen in Assembler implementiert und die Leistung gemessen.

Das Umschalten zwischen Coroutinen, auch Erlang-Prozessen genannt, benötigt auf einem modernen Prozessor etwa 16 Anweisungen und 20 Nanosekunden. Außerdem kennen Sie häufig den Prozess, zu dem Sie wechseln (Beispiel: Ein Prozess, der eine Nachricht in seiner Warteschlange empfängt, kann als direkte Übergabe vom aufrufenden Prozess an den empfangenden Prozess implementiert werden), damit der Scheduler nicht ins Spiel kommt es ist eine O (1) -Operation.

Das Wechseln von Betriebssystem-Threads dauert etwa 500 bis 1000 Nanosekunden, da Sie den Kernel aufrufen. Der OS-Thread-Scheduler wird möglicherweise in der Zeit O (log (n)) oder O (log (log (n))) ausgeführt. Dies macht sich bemerkbar, wenn Sie Zehntausende oder sogar Millionen von Threads haben.

Daher sind Erlang-Prozesse schneller und skalieren besser, da sowohl die grundlegende Umschaltoperation schneller ist als auch der Scheduler weniger häufig ausgeführt wird.


33

Erlang-Prozesse entsprechen (ungefähr) grünen Fäden in anderen Sprachen; Es gibt keine vom Betriebssystem erzwungene Trennung zwischen den Prozessen. (Es mag durchaus eine sprachgesteuerte Trennung geben, aber das ist ein geringerer Schutz, obwohl Erlang einen besseren Job macht als die meisten anderen.) Weil sie so viel leichter sind, können sie weitaus umfangreicher verwendet werden.

OS-Threads hingegen können einfach auf verschiedenen CPU-Kernen geplant werden und (meistens) unabhängige CPU-gebundene Verarbeitung unterstützen. Betriebssystemprozesse sind wie Betriebssystemthreads, jedoch mit einer viel stärkeren vom Betriebssystem erzwungenen Trennung. Der Preis für diese Funktionen ist, dass Betriebssystem-Threads und (noch mehr) Prozesse teurer sind.


Ein anderer Weg, um den Unterschied zu verstehen, ist dieser. Angenommen, Sie würden eine Implementierung von Erlang über die JVM schreiben (kein besonders verrückter Vorschlag), dann würden Sie jeden Erlang-Prozess zu einem Objekt mit einem bestimmten Status machen. Sie hätten dann einen Pool von Thread-Instanzen (normalerweise entsprechend der Anzahl der Kerne in Ihrem Host-System; dies ist ein einstellbarer Parameter in echten Erlang-Laufzeiten übrigens), die die Erlang-Prozesse ausführen. Dadurch wird die zu erledigende Arbeit auf die tatsächlich verfügbaren Systemressourcen verteilt. Es ist eine ziemlich nette Art, Dinge zu tun, aber es hängt absolut davon abauf die Tatsache, dass jeder einzelne Erlang-Prozess nicht viel bewirkt. Das ist natürlich in Ordnung; Erlang ist so strukturiert, dass diese einzelnen Prozesse nicht schwergewichtig sein müssen, da das gesamte Ensemble von ihnen das Programm ausführt.

In vielerlei Hinsicht ist das eigentliche Problem die Terminologie. Die Dinge, die Erlang Prozesse nennt (und die in CSP, CCS und insbesondere im π-Kalkül stark dem gleichen Konzept entsprechen), sind einfach nicht die gleichen wie die Dinge, die Sprachen mit einem C-Erbe (einschließlich C ++, Java, C # und viele andere) rufen einen Prozess oder einen Thread auf. Es gibt einige Ähnlichkeiten (alle beinhalten eine Vorstellung von gleichzeitiger Ausführung), aber es gibt definitiv keine Äquivalenz. Seien Sie also vorsichtig, wenn jemand zu Ihnen „Prozess“ sagt. sie könnten verstehen, dass es etwas völlig anderes bedeutet ...


3
Erlang kommt Pi Calculus nicht nahe. Pi-Kalkül geht von synchronen Ereignissen über Kanäle aus, die an Variablen gebunden werden können. Diese Art von Konzept passt überhaupt nicht zum Erlang-Modell. Versuchen Sie, sich Calculus anzuschließen, Erlang ist dem näher, obwohl es immer noch in der Lage sein muss, einige Nachrichten und so weiter nativ zu bearbeiten. Es gab ein Dissertationspapier (und ein Projekt) namens JErlang, das es implementierte.
Ich gebe schreckliche Ratschläge 29.

Es hängt alles davon ab, wie genau Sie den Pi-Kalkül sehen (und Sie können asynchrone Kanäle mit synchronen Kanälen plus Pufferprozessen modellieren).
Donal Fellows

Sie sagen nur, dass Erlang-Prozesse leichtgewichtig sind, aber Sie erklären nicht, warum sie einen geringeren Platzbedarf haben (leichtgewichtig sind) und warum sie eine bessere Leistung als Betriebssystem-Threads haben.
Jonas

1
@ Jonas: Für einige Arten von Aufgaben (insbesondere rechenintensive Aufgaben) sind Betriebssystem-Threads besser geeignet. Wohlgemerkt, dies sind normalerweise keine Aufgaben, für die Erlang verwendet wird. Erlang konzentriert sich auf eine große Anzahl einfacher Kommunikationsaufgaben. Dies hat unter anderem den Vorteil, dass bei einer Gruppe von Aufgaben, die ein Stück Arbeit erledigen und auf das Ergebnis warten, alles in einem einzigen Betriebssystem-Thread auf einem einzigen Prozessor erledigt werden kann, was effizienter ist als Kontextwechsel haben.
Donal Fellows

Theoretisch könnten Sie einen Betriebssystem-Thread auch sehr billig machen, indem Sie einen sehr kleinen Stapel verwenden und die Anzahl der anderen zugewiesenen threadspezifischen Ressourcen sorgfältig steuern. In der Praxis ist dies jedoch recht problematisch. (Das Vorhersagen von Stack-Anforderungen ist ein bisschen schwarz.) Stattdessen sind OS-Threads besonders darauf ausgelegt, optimal zu sein, wenn weniger davon vorhanden sind (in der Größenordnung der Anzahl der CPU-Kerne) und wenn sie eine größere Bedeutung haben Verarbeitungsmengen jeweils.
Donal Fellows

3

Ich denke, Jonas wollte einige Zahlen zum Vergleich von Betriebssystem-Threads mit Erlang-Prozessen. Der Autor von Programming Erlang, Joe Armstrong, hat vor einiger Zeit die Skalierbarkeit des Laichens von Erlang-Prozessen auf Betriebssystem-Threads getestet. Er schrieb einen einfachen Webserver in Erlang und testete ihn gegen Apache mit mehreren Threads (da Apache Betriebssystem-Threads verwendet). Es gibt eine alte Website mit Daten aus dem Jahr 1998. Ich habe es nur geschafft, diese Website genau einmal zu finden. Ich kann also keinen Link angeben. Aber die Informationen sind da draußen. Der Hauptpunkt der Studie zeigte, dass Apache knapp 8K-Prozesse maximal ausnutzte, während sein handgeschriebener Erlang-Server mehr als 10K-Prozesse abwickelte.


5
Ich denke, Sie sprechen über dieses: sics.se/~joe/apachevsyaws.html Aber ich fragte, wie erlang Threads im Vergleich zu Kerlenl-Threads so effizient macht.
Jonas

@ Jonas Link ist tot. Letzter Schnappschuss ist hier
alvaro g

1
In dem Artikel heißt es: "Apache stirbt bei ungefähr 4.000 parallelen Sitzungen. Yaws funktioniert immer noch bei über 80.000 parallelen Verbindungen."
Nathan Long

Den vollständigen Artikel finden Sie unter citeseerx.ist.psu.edu/viewdoc/…. In der Tat war es unmöglich, den Erlang-Server mit 16 angreifenden Maschinen zu beschädigen - obwohl es einfach war, den Apache-Server zu stoppen.
Bernhard

1

Da sich der Erlang-Interpreter nur um sich selbst kümmern muss, muss sich das Betriebssystem um viele andere Dinge kümmern.


0

Einer der Gründe dafür ist, dass der erlang-Prozess nicht im Betriebssystem, sondern in der evm (erlang virtual machine) erstellt wird, sodass die Kosten geringer sind.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.