RAID-Technologie verspricht höhere Performance und mehr Sicherheit beim permanenten Speichern von Daten. Die ADMIN-Redaktion gibt einen Überblick über ... (mehr)

Dynamisch erweitern

Für die folgenden Beispiele wird Julia nun auf einer Multicore-Maschine mit zwei Prozessoren gestartet:

julia -q -p2

Das dynamische Hinzufügen weiterer Cores wird im nächsten Abschnitt beschrieben, im Moment sollen nur zwei Cores benutzt werden. Es ist außerdem sinnvoll, die Anzahl Cores nicht zu überzeichnen (das »-p« -Argument soll also nicht die Anzahl Cores in der Maschine übersteigen).Das Beispiel benutzt einen »remote_call« , um zwei Zahlen auf einem andren Prozessor zu addieren. Danach wird das Resultat abgeholt. Man könnte genauso wieder einen Remote Call benutzen, um mit dem Ergebnis weiterzurechnen.

julia> r = remote_call(2,+,2,2)
RemoteRef(2,1,1)
julia> fetch(r)
4
julia> s = remote_call(2,+,1,r)
RemoteRef(2,1,2)
julia>fetch(s)
5

Remote Calls kehren unmittelbar zurück und warten nicht auf die Beendigung des Task. Der Prozessor, der den Call absetzte, fährt mit seiner nächsten Operation fort, während der Call irgendwo anders abgearbeitet wird. ein »fetch()« wartet allerdings bis das Resultat verfügbar ist. Alternativ kann man auf die Beendigung eines Remote Calls warten, indem man ein »wait()« mit der Remote Reference absetzt.

Das eben gezeigte Beispiel ist durch den Zwang, die Prozessornummern explizit anzugeben, nicht besonders portabel. Die meisten Programmierer benutzen deshalb ein Julia-Makro namens »spawn« , das diese Abhängigkeit eliminiert. Zum Beispiel:

julia> r= @spawn 7-1
RemoteRef(2,1,7)
julia> fetch(r)
6

Auch das Makro »@parallel« ist in Schleifen sehr nützlich. Weil Julia interaktiv ist, muss man sich ins Gedächtnis rufen, dass auf der Kommandozeile eingegebene Funktionen nicht automatisch entfernten Cores zur Verfügung stehen (auf dem lokalen oder einem entfernten Knoten). Um Funktionen auf allen Cores zu verwenden muss man »load()« benutzen. Diese Funktion lädt Julia-Programme automatisch auf alle Cores, die mit einer Julia-Instanz assoziiert sind. Alternativ liest Julia das File »startup.jl« im Homedirectory, wenn es existiert.

Alle Cores aufrufen

Julia kann Cores der lokalen Maschine oder von entfernten Maschinen (das sind immer Cluster-Nodes) nutzen. Wie das geht, wird im Folgenden demonstriert. Zuerst startet man die Julia-Instanz mit einem Core. Die »nprocs()« -Funktion zeigt die Anzahl verfügbarer Cores der aktuellen Instanz an:

$ julia -q
julia>nprocs()
1

Möchte man nun weitere lokale Cores hinzufügen, bietet sich die Funktion »addprocs_local()« an, wie Listing 2 zeigt. In diesem Beispiel wurde ein Core hinzugefügt und nprocs() zeigt daraufhin zwei Cores an.

Listing 2

addprocs

 

julia>nprocs()
2

Remote Cores (solche von anderen Maschinen) lassen sich auf zweierlei Weise hinzufügen. Einmal mithilfe der »addprocs_ssh« -Funktion. Ein Beispiel zeigt Listing 3, das je einen Core von den Knoten n0 und n2 einbindet. Die Voraussetzung dafür ist allerdings, dass Julia auf allen Nodes an derselben Stelle installiert oder über ein Shared Filesystem erreichbar ist. Außerdem muss die PATH-Variable auf den entfernten Knoten das Verzeichnis des Julia-Binaries enthalten. Die Anzahl der Prozessoren hat sich jetzt auf vier erhöht.

Listing 3

Nodes hinzufügen

 

Um zu überprüfen, ob die entfernten Cores tatsächlich einbezogen werden, kann man eine einfache parallel Schleife mithilfe des »@parallel« -Makros ausführen.

julia> @parallel for i=1:4
 run(`hostname`)
 end
julia>limulus
limulus
n2
n0

Julia verwendet hier alle verfügbaren Cores und die Hostnamen zeigen, dass es sich um zwei lokale und zwei entfernte Cores handelt (die lokale Maschine heißt limulus). Die Knoten werden in Round-Robin-Manier benutzt. Wenn sich der Endwert der Schleife erhöht, benutzt Julia die verfügbaren Cores reihum:

julia> @parallel for i=1:8
 run(`hostname`)
 end
julia>limulus
limulus
limulus
limulus
n2
n0
n2
n0

Ein Beispiel aus der Julia Dokumentation [9] vermittelt ein besseres Gefühl für die Parallelverarbeitung. Zuerst sollte man sich ansehen, wie es mit einem Core funktioniert. Das folgende Programm generiert Zufallsbits (» « oder »1« ) und summiert sie auf. Die »tic()« -funktion startet dabei einen Timer und »toc()« gibt das Ergebnis aus.

julia>tic();
nheads = @parallel (+) for i=1:1000000000
randbit()end;
s=toc();
println("Number of Heads: $nheads in $s seconds")
elapsed time: 11.234276056289673 seconds
Numberof Heads: 50003873 in 11.234276056289673 seconds

Die Schleife braucht mit einem Core 11,23 Sekunden. Lässt man nun genau die gleiche Schleife laufen, benutzt aber zwei lokale und 2 entfernte Cores, braucht sie 5,67 Sekunden. Verwendet man schließlich vier lokale Cores, kommt man auf 3,71 Sekunden.

Ähnliche Artikel

comments powered by Disqus

Artikel der Woche

Eigene Registry für Docker-Images

Wer selber Docker-Images herstellt, braucht auch eine eigene Registry. Diese gibt es ebenfalls als Docker-Image, aber nur mit eingeschränkter Funktionalität. Mit einem Auth-Server wird daraus ein brauchbares Repository für Images. (mehr)
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Konfigurationsmanagement

Ich konfiguriere meine Server

  • von Hand
  • mit eigenen Skripts
  • mit Puppet
  • mit Ansible
  • mit Saltstack
  • mit Chef
  • mit CFengine
  • mit dem Nix-System
  • mit Containern
  • mit anderer Konfigurationsmanagement-Software

Google+

Ausgabe /2019