Der Zweck des zukünftigen Pakets ist es, eine sehr einfache und einheitliche Möglichkeit zur Bewertung von R -Ausdrücken asynchron zu bieten, indem verschiedene Ressourcen für den Benutzer zur Verfügung stehen.
In der Programmierung ist eine Zukunft eine Abstraktion für einen Wert , der möglicherweise irgendwann in der Zukunft verfügbar ist. Der Zustand einer Zukunft kann entweder ungelöst oder gelöst sein. Sobald es gelöst ist, ist der Wert sofort verfügbar. Wenn der Wert während der Zukunft noch ungelöst ist, wird der aktuelle Prozess blockiert, bis die Zukunft behoben ist. Es ist möglich zu prüfen, ob eine Zukunft behoben ist oder nicht, ohne zu blockieren. Genau wie und wann Futures gelöst werden, hängt davon ab, welche Strategie zur Bewertung verwendet wird. Beispielsweise kann eine Zukunft mit einer sequentiellen Strategie gelöst werden, was bedeutet, dass sie in der aktuellen R -Sitzung gelöst wird. Andere Strategien können darin bestehen, Futures asynchron zu beheben, beispielsweise durch die parallele Bewertung der Ausdrücke auf der aktuellen Maschine oder gleichzeitig auf einem Rechencluster.
Hier ist ein Beispiel, das veranschaulicht, wie die Grundlagen der Futures funktionieren. Betrachten Sie zunächst den folgenden Code -Snippet, der Plain R -Code verwendet:
> v <- {
+ cat( " Hello world! n " )
+ 3.14
+ }
Hello world !
> v
[ 1 ] 3.14
Es funktioniert, indem es den Wert eines Ausdrucks an Variable v
zuweist, und wir drucken dann den Wert von v
. Wenn der Ausdruck für v
bewertet wird, drucken wir außerdem auch eine Nachricht.
Hier ist der gleiche Code -Snippet, der geändert wurde, um stattdessen Futures zu verwenden:
> library( future )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
Der Unterschied besteht darin, wie v
konstruiert ist; Mit einfachem verwenden wir <-
während wir mit Futures %<-%
verwenden. Der andere Unterschied besteht darin, dass die Ausgabe nach Lösung der Zukunft (nicht während) und wenn der Wert abgefragt wird (siehe Vignette 'Ausgabetxt'), weitergeleitet wird.
Warum sind Futures nützlich? Da wir den zukünftigen Ausdruck in einem separaten R -Prozess asynchron bewerten können, indem wir einfach die Einstellungen als:
> library( future )
> plan( multisession )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
Mit asynchronen Futures blockiert der aktuelle/Haupt -R -Prozess nicht , was bedeutet, dass er für die weitere Verarbeitung verfügbar ist, während die Futures in separaten Prozessen, die im Hintergrund ausgeführt werden, aufgelöst werden. Mit anderen Worten, Futures bieten ein einfaches, aber dennoch leistungsstarkes Konstrukt für parallele und / oder verteilte Verarbeitung in R.
Wenn Sie sich nicht die Mühe machen können, alle Details zu Futures zu lesen, sondern nur die detaillierten Details auszuprobieren, dann gehen Sie zum Ende, um mit der Mandelbrot-Demo zu spielen, indem Sie sowohl parallel als auch nicht parallele Bewertung verwenden.
Futures können entweder implizit oder explizit erstellt werden. Im obigen Einführungsbeispiel haben wir implizite Futures verwendet, die über das v %<-% { expr }
-Konstrukt erstellt wurden. Eine Alternative ist explizite Futures, die die Konstrukte von f <- future({ expr })
und v <- value(f)
verwenden. Mit diesen könnte unser Beispiel alternativ als:
> library( future )
> f <- future({
+ cat( " Hello world! n " )
+ 3.14
+ })
> v <- value( f )
Hello world !
> v
[ 1 ] 3.14
Beide Art des zukünftigen Konstrukts funktioniert gleich (*) gut. Der implizite Stil ähnelt am ähnlichsten, wie der reguläre R -Code geschrieben wird. Grundsätzlich müssen Sie lediglich <-
durch einen %<-%
ersetzen, um die Zuordnung in eine zukünftige Zuordnung zu verwandeln. Andererseits kann diese Einfachheit auch täuschen, insbesondere wenn asynchrone Futures verwendet werden. Im Gegensatz dazu macht der explizite Stil viel klarer, dass Futures verwendet werden, was das Risiko für Fehler verringert und das Design besser mit anderen Lesen Ihres Codes weiterleitet.
(*) Es gibt Fälle, in denen %<-%
ohne einige (kleine) Modifikationen nicht verwendet werden kann. Wir werden in Abschnitt "Einschränkungen, wenn sie implizite Futures verwenden" gegen Ende dieses Dokuments zurückkehren.
Zusammenfassend nutzen wir für explizite Zukunft: wir verwenden:
f <- future({ expr })
- Erstellt eine Zukunftv <- value(f)
- Ruft den Wert der Zukunft ab (Blöcke, falls dies noch nicht aufgelöst wird)Für implizite Zukunft verwenden wir:
v %<-% { expr }
- erstellt eine Zukunft und ein Versprechen für ihren WertUm es einfach zu halten, werden wir den impliziten Stil im Rest dieses Dokuments verwenden, aber alles, was besprochen wird, gilt auch für explizite Zukunft.
Das zukünftige Paket implementiert die folgenden Arten von Futures:
Name | Oose | Beschreibung |
---|---|---|
synchron: | Nichtparallel: | |
sequential | alle | sequentiell und im aktuellen R -Prozess |
asynchron: | parallel : | |
multisession | alle | Hintergrund -R -Sitzungen (auf der aktuellen Maschine) |
multicore | Nicht Windows/nicht RSTUDIO | Forked R -Prozesse (auf aktueller Maschine) |
cluster | alle | Externe R -Sitzungen auf aktuellen, lokalen und/oder Fernmaschinen |
Das zukünftige Paket ist so gestaltet, dass auch Unterstützung für zusätzliche Strategien implementiert werden kann. Zum Beispiel bietet das Paket für Future.Callr zukünftige Backends, die Futures in einem Hintergrund -R -Prozess unter Verwendung des CALLR -Pakets bewerten - sie funktionieren ähnlich wie multisession
-Futures, haben jedoch einige Vorteile. Das Paket für Future.BatchTools bietet Futures für alle Arten von Clusterfunktionen ("Backends"), die das Batchtools -Paket unterstützt. Insbesondere sind auch Futures zur Bewertung von R -Ausdrücken über Stellenplaner wie Slurm, Drehmoment/PBS, Oracle/Sun Grid Engine (SGE) und Lastfreigabeanlage (LSF) verfügbar.
Standardmäßig werden zukünftige Ausdrücke eifrig (= augenblicklich) und synchron (in der aktuellen R -Sitzung) bewertet. Diese Bewertungsstrategie wird als "sequentiell" bezeichnet. In diesem Abschnitt werden wir jede dieser Strategien durchlaufen und diskutieren, was sie gemeinsam haben und wie sie sich unterscheiden.
Bevor Sie jede der verschiedenen zukünftigen Strategien durchlaufen, ist es wahrscheinlich hilfreich, die Ziele der zukünftigen API (wie durch das zukünftige Paket definiert) zu klären. Bei der Programmierung mit Futures sollte es nicht wirklich wichtig sein, welche zukünftige Strategie für die Ausführung von Code verwendet wird. Dies liegt daran, dass wir nicht wirklich wissen können, auf welche Rechenressourcen der Benutzer zugegriffen hat. Mit anderen Worten, der Code sollte keine Annahmen über die Art der verwendeten Futures treffen, z. B. synchron oder asynchron.
Einer der Entwürfe der zukünftigen API war es, alle Unterschiede zu verkörpern, so dass alle Arten von Futures gleich zu funktionieren scheinen. Dies trotz Ausdrücke kann in der aktuellen R -Sitzung oder auf der ganzen Welt in den Remote -R -Sitzungen lokal bewertet werden. Ein weiterer offensichtlicher Vorteil einer konsistenten API und einem konsistenten Verhalten zwischen verschiedenen Arten von Futures besteht darin, dass sie beim Prototyping hilft. Normalerweise würde man beim Erstellen eines Skripts eine sequentielle Bewertung verwenden, und später, wenn das Skript vollständig entwickelt ist, kann man die asynchrone Verarbeitung einschalten.
Aus diesem Grund sind die Standardeinstellungen der verschiedenen Strategien so, dass die Ergebnisse und Nebenwirkungen der Bewertung eines zukünftigen Ausdrucks so ähnlich wie möglich sind. Insbesondere gilt das Folgende für alle Zukunft:
Die gesamte Bewertung erfolgt in einer lokalen Umgebung (dh local({ expr })
), sodass die Aufgaben nicht die Anrufumgebung beeinflussen. Dies ist natürlich bei der Bewertung in einem externen R -Prozess, wird jedoch auch bei der Bewertung in der aktuellen R -Sitzung durchgesetzt.
Wenn eine Zukunft konstruiert wird, werden globale Variablen identifiziert . Für eine asynchrone Bewertung werden Globale in den R -Prozess/die R -Sitzung exportiert, die den zukünftigen Ausdruck bewerten. Für sequentielle Zukunft mit fauler Bewertung ( lazy = TRUE
) sind Globale "eingefroren" (in ein lokales Umfeld der Zukunft geklont). Um versehentlich vor dem Export von zu großen Objekten zu schützen, besteht die integrierte Behauptung, dass die Gesamtgröße aller Globalen weniger als ein bestimmter Schwellenwert ist (über eine Option steuerbar, vgl. help("future.options")
). Wenn der Schwellenwert überschritten wird, wird ein informativer Fehler geworfen.
Zukünftige Ausdrücke werden nur einmal bewertet . Sobald der Wert (oder ein Fehler) gesammelt wurde, wird er für alle nachfolgenden Anfragen verfügbar sein.
Hier ist ein Beispiel, das veranschaulicht, dass alle Aufgaben in eine lokale Umgebung erfolgen:
> plan( sequential )
> a <- 1
> x % <- % {
+ a <- 2
+ 2 * a
+ }
> x
[ 1 ] 4
> a
[ 1 ] 1
Jetzt sind wir bereit, die verschiedenen zukünftigen Strategien zu erkunden.
Synchrone Futures werden nacheinander und am häufigsten durch den R -Prozess, der sie erstellt, gelöst. Wenn eine synchrone Zukunft gelöst wird, blockiert sie den Hauptprozess bis zur Auflösung.
Sequentielle Futures sind die Standardeinstellung, sofern nicht anders angegeben. Sie sollten sich so ähnlich wie möglich wie möglich verhalten und gleichzeitig die zukünftige API und ihr Verhalten erfüllten. Hier ist ein Beispiel, das ihre Eigenschaften veranschaulicht:
> plan( sequential )
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437557
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
Da eine eifrige sequentielle Bewertung stattfindet, wird jede der drei Futures in dem Moment, in dem sie erstellt wird, sofort aufgelöst. Beachten Sie auch, wie pid
in der Anrufumgebung, der die Prozess -ID des aktuellen Prozesses zugewiesen wurde, weder überschrieben noch entfernt wird. Dies liegt daran, dass Futures in einer lokalen Umgebung bewertet werden. Da die synchrone (UNI-)-Verarbeitung verwendet wird, wird Future b
durch den Haupt-R-Prozess (noch in einer lokalen Umgebung) gelöst, weshalb der Wert von b
und pid
gleich sind.
Als nächstes werden wir uns an asynchrone Zukunftsfutures zuwenden, die im Hintergrund aufgelöst werden. Entworfen sind diese Futures nicht blockiert, dh nach der Erstellung des Aufrufprozesses ist für andere Aufgaben verfügbar, einschließlich der Erstellung zusätzlicher Futures. Erst wenn der Anrufprozess versucht, auf den Wert einer Zukunft zuzugreifen, die noch nicht gelöst ist, oder versuchen, eine andere asynchrone Zukunft zu erstellen, wenn alle verfügbaren R -Prozesse damit beschäftigt sind, andere Zukunft zu bedienen, blockiert er.
Wir beginnen mit Multisions -Futures, weil sie von allen Betriebssystemen unterstützt werden. Eine Multisions -Zukunft wird in einer Hintergrund -R -Sitzung bewertet, die auf demselben Maschine wie der aufrufende R -Prozess ausgeführt wird. Hier ist unser Beispiel mit Multisionsbewertung:
> plan( multisession )
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437616
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
Das erste, was wir beobachten, ist, dass die Werte von a
, c
und pid
wie zuvor die gleichen sind. Wir stellen jedoch fest, dass b
von zuvor anders ist. Dies liegt daran, dass Future b
in einem anderen R -Prozess bewertet wird und daher eine andere Prozess -ID zurückgibt.
Wenn die Multisionsbewertung verwendet wird, startet das Paket eine Reihe von R -Sitzungen im Hintergrund, die durch die Bewertung ihrer Ausdrücke bei der Erstellung von Multisions -Futures dienen. Wenn alle Hintergrundsitzungen damit beschäftigt sind, andere Zukunft zu bedienen, wird die Erstellung der nächsten Multisession -Zukunft blockiert, bis eine Hintergrundsitzung erneut verfügbar ist. Die Gesamtzahl der gestarteten Hintergrundprozesse wird durch den Wert von availableCores()
entschieden, z. B.
> availableCores()
mc.cores
2
Dieses spezielle Ergebnis zeigt uns, dass die Option mc.cores
so festgelegt wurde, dass wir in insgesamt zwei (2) Prozessen einschließlich des Hauptprozesses verwenden dürfen. Mit anderen Worten, mit diesen Einstellungen wird es zwei (2) Hintergrundprozesse für Multisession -Futures geben. Die availableCores()
ist auch agil gegenüber verschiedenen Optionen und Systemumgebungsvariablen. Wenn beispielsweise Berechnungscluster -Scheduler verwendet werden (z. B. Drehmoment/PBS und Slurm), setzen sie eine spezifische Umgebungsvariable fest, in der die Anzahl der Kerne angegeben ist, die einem bestimmten Job zugeteilt wurden. availableCores()
erkennt diese ebenfalls an. Wenn nichts anderes angegeben ist, werden alle verfügbaren Kerne auf der Maschine verwendet, vgl. parallel::detectCores()
. Weitere Informationen finden Sie unter help("availableCores", package = "parallelly")
.
Bei Betriebssystemen, bei denen R die Gabering von Prozessen unterstützt, das im Grunde genommen das gesamte Betriebssystem mit Ausnahme von Windows ist, besteht eine Alternative zu Laichen im Hintergrund darin, den vorhandenen R -Prozess zu verabschieden. Um Multicore -Futures bei der Unterstützung zu verwenden, geben Sie an:
plan( multicore )
Genau wie bei Multisions -Futures wird die maximale Anzahl paralleler Prozesse von availableCores()
festgelegt, da in beiden Fällen die Bewertung auf der lokalen Maschine durchgeführt wird.
Das Abgabeding eines R -Prozesses kann schneller sein als mit einer separaten R -Sitzung im Hintergrund zu arbeiten. Ein Grund dafür ist, dass der Aufwand des Exportierens großer Global in die Hintergrundsitzung größer sein kann als beim Gabeln und damit der gemeinsame Speicher wird verwendet. Andererseits wird der gemeinsame Speicher nur gelesen , was bedeutet, dass alle Änderungen an gemeinsam genutzten Objekten durch einen der Forked -Prozesse ("Arbeitnehmer") eine Kopie durch das Betriebssystem verursachen. Dies kann auch passieren, wenn der R -Müllsammler in einem der gegenteilten Prozesse läuft.
Andererseits wird in einigen R -Umgebungen auch die Prozessvergasung als instabil angesehen. Zum Beispiel kann beim Ausführen von R aus dem RSTUDIO -Prozess die Abgabeding zu Absturz R -Sitzungen führen. Aus diesem Grund deaktiviert das zukünftige Paket Multicore -Futures standardmäßig beim Ausführen von RStudio. Weitere Informationen finden Sie help("supportsMulticore")
.
Cluster-Futures bewerten Ausdrücke in einem Ad-hoc-Cluster (wie im Parallelpaket implementiert). Angenommen, Sie haben Zugriff auf drei Knoten n1
, n2
und n3
. Sie können diese dann für eine asynchrone Bewertung verwenden wie:
> plan( cluster , workers = c( " n1 " , " n2 " , " n3 " ))
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437715
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
Alle Arten von Clustern, die parallel::makeCluster()
erstellen, können für Cluster -Futures verwendet werden. Zum Beispiel kann der obige Cluster explizit eingerichtet werden wie:
cl <- parallel :: makeCluster(c( " n1 " , " n2 " , " n3 " ))
plan( cluster , workers = cl )
Außerdem wird es als guter Stil angesehen, Cluster cl
zu schließen, wenn es nicht mehr benötigt wird, das heißt, parallel::stopCluster(cl)
. Es wird sich jedoch abschließen, wenn der Hauptprozess beendet wird. Weitere Informationen zum Einrichten und Verwalten solcher Cluster finden Sie help("makeCluster", package = "parallel")
. Cluster, die implizit mit plan(cluster, workers = hosts)
erstellt wurden, wobei hosts
ein Zeichenvektor sind, wird ebenfalls geschlossen, wenn die Haupt -R -Sitzungssitzung endet oder wenn die zukünftige Strategie geändert wird, z. B. durch plan(sequential)
.
Beachten Sie, dass bei der automatischen Authentifizierungs -Setup (z. B. SSH -Schlüsselpaare) nichts verhindern, dass wir denselben Ansatz für die Verwendung eines Cluster von Remote -Maschinen verwenden.
Wenn Sie mehrere Mitarbeiter auf jedem Knoten ausführen möchten, replizieren Sie den Knotennamen so oft wie die Anzahl der Arbeitnehmer, die auf diesem Knoten ausgeführt werden. Zum Beispiel,
> plan(cluster, workers = c(rep("n1", times = 3), "n2", rep("n3", times = 5)))
Wird drei Arbeiter auf n1
, einen auf n2
und fünf auf n3
insgesamt neun parallelen Arbeitern betreiben.
So weit haben wir diskutiert, was als "flache Topologie" der Futures bezeichnet werden kann, dh alle Futures werden in derselben Umgebung erstellt und zugeordnet. Es hindert uns jedoch nichts daran, eine "verschachtelte Topologie" von Futures zu verwenden, in der ein Satz von Futures wiederum eine weitere Reihe von Futures in intern und so weiter erzeugen kann.
Zum Beispiel finden Sie hier ein Beispiel für zwei "Top" -Futures ( a
und b
), bei dem die Multisionsbewertung verwendet wird und bei der die zweite Zukunft ( b
) wiederum zwei interne Futures verwendet:
> plan( multisession )
> pid <- Sys.getpid()
> a % <- % {
+ cat( " Future 'a' ... n " )
+ Sys.getpid()
+ }
> b % <- % {
+ cat( " Future 'b' ... n " )
+ b1 % <- % {
+ cat( " Future 'b1' ... n " )
+ Sys.getpid()
+ }
+ b2 % <- % {
+ cat( " Future 'b2' ... n " )
+ Sys.getpid()
+ }
+ c( b.pid = Sys.getpid(), b1.pid = b1 , b2.pid = b2 )
+ }
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437804
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437805 1437805 1437805
Durch die Prüfung der Prozess -IDs sehen wir, dass insgesamt drei verschiedene Prozesse zur Lösung der Futures beteiligt sind. Es gibt den Haupt -R -Prozess (PID 1437557), und es gibt die beiden Prozesse, die von a
(PID 1437804) und b
(PID 1437805) verwendet werden. Die beiden Futures ( b1
und b2
), die durch b
verschachtelt sind, werden jedoch durch denselben R -Prozess wie b
bewertet. Dies liegt daran, dass verschachtelte Futures eine sequentielle Bewertung verwenden, sofern nicht anders angegeben. Dafür gibt es einige Gründe, aber der Hauptgrund ist, dass es uns vor Fehler vor rekursiven Anrufen vor rekursivem Aufrufen vor rekursiven Anrufen schützt.
Um eine andere Art von Evaluierungs -Topologie anzugeben, außer der ersten Ebene der Futures, die durch Multisionsbewertung und die zweite Ebene durch sequentielle Bewertung gelöst werden, können wir eine Liste von Bewertungsstrategien für plan()
. Erstens können dieselben Bewertungsstrategien wie oben explizit festgelegt werden wie:
plan( list ( multisession , sequential ))
Wir würden tatsächlich das gleiche Verhalten bekommen, wenn wir es mit mehreren Ebenen an Multisionsbewertungen versuchen.
> plan( list ( multisession , multisession ))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437901
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437902 1437902 1437902
Der Grund dafür ist auch hier, uns davor zu schützen, mehr Prozesse zu starten, als die Maschine unterstützen kann. Intern wird dies durch Einstellen mc.cores = 1
durchgeführt, sodass Funktionen wie parallel::mclapply()
zurückfallen, um nacheinander auszuführen. Dies gilt sowohl für die Multision als auch für die Multicore -Bewertung.
Wenn wir mit sequentieller Bewertung beginnen und dann die Multisionsbewertung für verschachtelte Futures verwenden, erhalten wir:
> plan( list ( sequential , multisession ))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437557
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437557 1438017 1438016
Dies zeigt deutlich, dass a
und b
im Anrufprozess (PID 1437557) gelöst werden, während die beiden verschachtelten Futures ( b1
und b2
) in zwei separaten R -Prozessen (PIDs 1438017 und 1438016) aufgelöst werden.
Trotzdem ist es in der Tat möglich, verschachtelte Multisionsbewertungsstrategien zu verwenden, wenn wir die Anzahl der auf jeder Ebene verfügbaren Kerne ausdrücklich angeben ( lesen ). Um dies zu tun, müssen wir die Standardeinstellungen "optimieren", was wie folgt durchgeführt werden kann:
> plan( list (tweak( multisession , workers = 2 ), tweak( multisession ,
+ workers = 2 )))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1438105
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1438106 1438211 1438212
Erstens sehen wir, dass sowohl a
als auch b
in verschiedenen Prozessen (PIDs 1438105 und 1438106) als der aufrufende Prozess (PID 1437557) aufgelöst werden. Zweitens werden die beiden verschachtelten Futures ( b1
und b2
) in zwei weiteren R -Prozessen (PIDs 1438211 und 1438212) gelöst.
Weitere Informationen zur Arbeit mit verschachtelten Futures und verschiedenen Bewertungsstrategien auf jeder Ebene finden Sie in Vignette 'Futures in R: zukünftigen Topologien'.
Es ist möglich zu prüfen, ob eine Zukunft gelöst wurde oder nicht, ohne zu blockieren. Dies kann anhand der resolved(f)
Funktion (F) erfolgen, die eine explizite Zukunft f
als Eingabe nimmt. Wenn wir mit impliziten Futures arbeiten (wie in allen oben genannten Beispielen), können wir die f <- futureOf(a)
-Funktion verwenden, um die explizite Zukunft von einem impliziten abzurufen. Zum Beispiel,
> plan( multisession )
> a % <- % {
+ cat( " Future 'a' ... " )
+ Sys.sleep( 2 )
+ cat( " done n " )
+ Sys.getpid()
+ }
> cat( " Waiting for 'a' to be resolved ... n " )
Waiting for ' a ' to be resolved ...
> f <- futureOf( a )
> count <- 1
> while ( ! resolved( f )) {
+ cat( count , " n " )
+ Sys.sleep( 0.2 )
+ count <- count + 1
+ }
1
2
3
4
5
6
7
8
9
10
> cat( " Waiting for 'a' to be resolved ... DONE n " )
Waiting for ' a ' to be resolved ... DONE
> a
Future ' a ' ... done
[ 1 ] 1438287
Manchmal ist die Zukunft nicht das, was Sie erwartet haben. Wenn bei der Bewertung einer Zukunft ein Fehler auftritt, wird der Fehler ausbreitet und als Fehler in der aufrufenden Umgebung geworfen , wenn der zukünftige Wert angefordert wird . Wenn wir beispielsweise eine faule Bewertung für eine Zukunft verwenden, die einen Fehler erzeugt, sehen wir möglicherweise so etwas wie
> plan( sequential )
> b <- " hello "
> a % <- % {
+ cat( " Future 'a' ... n " )
+ log( b )
+ } % lazy % TRUE
> cat( " Everything is still ok although we have created a future that will fail. n " )
Everything is still ok although we have created a future that will fail.
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
Der Fehler wird jedes Mal geworfen, wenn der Wert angefordert wird. Wenn wir versuchen, den Wert erneut zu erhalten, erzeugt der gleiche Fehler (und Ausgabe):
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
In addition : Warning message :
restarting interrupted promise evaluation
Um den letzten Anruf im Anrufstapel zu sehen, der den Fehler ergab, können wir die backtrace()
-Funktion (*) in der Zukunft verwenden, d.
> backtrace( a )
[[ 1 ]]
log( a )
(*) Der häufig verwendete traceback()
liefert keine relevanten Informationen im Kontext von Futures. Darüber hinaus ist es leider nicht möglich, die Liste der Anrufe (ausgewertete Ausdrücke) anzuzeigen, die zum Fehler geführt haben. Nur der Anruf, der den Fehler ergab (dies ist auf eine Einschränkung in tryCatch()
in internem verwendeten).
Immer wenn ein R -Ausdruck asynchron (parallel) oder nacheinander über eine faule Bewertung bewertet werden soll, müssen globale (alias "freie") Objekte identifiziert und an den Evaluator übergeben werden. Sie müssen genau so bestanden werden, wie sie zu der Zeit, als die Zukunft geschaffen wurde, bestanden werden, da sich die Globale für faule Bewertung ansonsten zwischen dem Erstellen und der Auflösung ändern können. Für die asynchrone Verarbeitung ist der Grund, warum Globale identifiziert werden müssen, so dass sie in den Prozess exportiert werden können, der die Zukunft bewertet.
Das zukünftige Paket versucht, diese Aufgaben so weit wie möglich zu automatisieren. Dies erfolgt mit Hilfe des Globals-Pakets, das eine statische Code-Inspektion verwendet, um globale Variablen zu identifizieren. Wenn eine globale Variable identifiziert wird, wird sie für den Bewertungsprozess erfasst und zur Verfügung gestellt. Wenn ein Global in einem Paket definiert ist, wird diese globale nicht exportiert. Stattdessen wird sichergestellt, dass das entsprechende Paket bei der Bewertung der Zukunft beigefügt ist. Dies spiegelt nicht nur besser die Einrichtung der Haupt -R -Sitzung wider, sondern minimiert auch die Notwendigkeit des Exportierens von Globalen, was nicht nur Speicher, sondern auch Zeit und Bandbreite spart, insbesondere bei der Verwendung von Remote -Rechenknoten.
Schließlich sollte klargestellt werden, dass die Identifizierung von Globalen aus statischer Codeinspektion allein ein herausforderndes Problem ist. Es wird immer Eckfälle geben, in denen die automatische Identifizierung von Globalen fehlschlägt, so dass entweder falsche Globale identifiziert werden (weniger Anliegen) oder einige der wahren Globals fehlen (was zu einem Laufzeitfehler oder möglicherweise zu den falschen Ergebnissen führt). Vignette 'Futures in R: Häufige Probleme mit Lösungen liefert Beispiele für gemeinsame Fälle und erklärt, wie sie sie vermeiden und wie das Paket helfen kann, Globale zu identifizieren oder fälschlicherweise Globale zu ignorieren. Wenn dies nicht ausreicht, ist es immer möglich globals = list(a = 42, slow_sum = my_sum)
die globalen Variablen mit ihren Namen manuell anzugeben ( globals = c("a", "slow_sum")
globals = list(a = 42, slow_sum = my_sum)
).
Es gibt eine Einschränkung mit impliziten Zukunft, die für explizite nicht existiert. Weil eine explizite Zukunft genau wie jedes andere Objekt in R ist, kann sie überall/an alles zugeordnet werden. Zum Beispiel können wir einige von ihnen in einer Schleife erstellen und sie einer Liste zuweisen, z. B.
> plan( multisession )
> f <- list ()
> for ( ii in 1 : 3 ) {
+ f [[ ii ]] <- future({
+ Sys.getpid()
+ })
+ }
> v <- lapply( f , FUN = value )
> str( v )
List of 3
$ : int 1438377
$ : int 1438378
$ : int 1438377
Dies ist bei der Verwendung implizite Futures nicht möglich. Dies liegt daran, dass der %<-%
Zuweisungsoperator nicht in allen Fällen verwendet werden kann, in denen der reguläre <-
-Zuweisungsbetreiber verwendet werden kann. Es kann nur verwendet werden, um Umgebungen (einschließlich der aufrufenden Umgebung) zukünftige Werte zuzuweisen, ähnlich wie assign(name, value, envir)
funktioniert. Wir können jedoch Umgebungen mit den benannten Indizes impliziten Futures zuweisen, z. B.
> plan( multisession )
> v <- new.env()
> for ( name in c( " a " , " b " , " c " )) {
+ v [[ name ]] % <- % {
+ Sys.getpid()
+ }
+ }
> v <- as.list( v )
> str( v )
List of 3
$ a : int 1438485
$ b : int 1438486
$ c : int 1438485
Hier blockiert as.list(v)
bis alle Futures in der Umgebung v
gelöst wurden. Dann werden ihre Werte als reguläre Liste gesammelt und zurückgegeben.
Wenn numerische Indizes erforderlich sind, können Listenumgebungen verwendet werden. Listenumgebungen, die vom Listenv -Paket implementiert werden, sind regelmäßige Umgebungen mit individuellen Unterlassbetreibern, die es ermöglichen, sie ähnlich zu indizieren, wie Listen indiziert werden können. Durch die Verwendung von Listenumgebungen, in denen wir sonst Listen verwenden würden, können wir impliziten Futures für listenähnliche Objekte mithilfe numerischer Indizes auch impliziten Futures zuweisen. Zum Beispiel,
> library( listenv )
> plan( multisession )
> v <- listenv()
> for ( ii in 1 : 3 ) {
+ v [[ ii ]] % <- % {
+ Sys.getpid()
+ }
+ }
> v <- as.list( v )
> str( v )
List of 3
$ : int 1438582
$ : int 1438583
$ : int 1438582
Wie zuvor blockiert as.list(v)
bis alle Futures gelöst sind.
Um eine Live -Illustration zu sehen, wie unterschiedliche Futures -Arten bewertet werden, führen Sie die Mandelbrot -Demo dieses Pakets aus. Versuchen Sie zunächst mit der sequentiellen Bewertung,
library( future )
plan( sequential )
demo( " mandelbrot " , package = " future " , ask = FALSE )
Das ähnelt, wie das Skript ausgeführt wird, wenn keine Futures verwendet würden. Versuchen Sie dann eine Multisionsbewertung, die die verschiedenen Mandelbrot -Flugzeuge unter Verwendung paralleler R -Prozesse berechnet, die im Hintergrund ausgeführt werden. Versuchen,
plan( multisession )
demo( " mandelbrot " , package = " future " , ask = FALSE )
Wenn Sie Zugriff auf mehrere Maschinen haben, können Sie versuchen, eine Gruppe von Arbeitern einzurichten und sie zu verwenden, z. B.
plan( cluster , workers = c( " n2 " , " n5 " , " n6 " , " n6 " , " n9 " ))
demo( " mandelbrot " , package = " future " , ask = FALSE )
R -Paket Zukunft ist auf Cran erhältlich und kann in R als: installiert werden:
install.packages( " future " )
Um die in Git Branch develop
Pre-Veröffentlichungsversion zu installieren.
remotes :: install_github( " futureverse/future " , ref = " develop " )
Dadurch wird das Paket von Quelle installiert.
Um zu diesem Paket beizutragen, sehen Sie sich bitte an.