El propósito del paquete futuro es proporcionar una forma muy simple y uniforme de evaluar las expresiones R de forma asincrónica utilizando varios recursos disponibles para el usuario.
En la programación, un futuro es una abstracción para un valor que puede estar disponible en algún momento en el futuro. El estado de un futuro puede no resolverse o resolverse . Tan pronto como se resuelve, el valor está disponible instantáneamente. Si el valor se consulta mientras el futuro aún no se ha resuelto, el proceso actual se bloquea hasta que se resuelve el futuro. Es posible verificar si un futuro está resuelto o no sin bloquear. Exactamente cómo y cuándo se resuelven los futuros depende de qué estrategia se use para evaluarlos. Por ejemplo, un futuro se puede resolver utilizando una estrategia secuencial, lo que significa que se resuelve en la sesión R actual. Otras estrategias pueden ser resolver futuros de manera asincrónica, por ejemplo, evaluando expresiones en paralelo en la máquina actual o simultáneamente en un clúster de cómputo.
Aquí hay un ejemplo que ilustra cómo funcionan los conceptos básicos de futuros. Primero, considere el siguiente fragmento de código que usa el código R Plain:
> v <- {
+ cat( " Hello world! n " )
+ 3.14
+ }
Hello world !
> v
[ 1 ] 3.14
Funciona asignando el valor de una expresión a la variable v
y luego imprimimos el valor de v
. Además, cuando se evalúa la expresión para v
también imprimimos un mensaje.
Aquí está el mismo fragmento de código modificado para usar futuros en su lugar:
> library( future )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
La diferencia está en cómo se construye v
; Con Rencion R usamos <-
mientras que con futuros usamos %<-%
. La otra diferencia es que la salida se retransmite después de que se resuelve el futuro (no durante) y cuando se consulta el valor (consulte la viñeta 'Texto de salida').
Entonces, ¿por qué son útiles los futuros? Porque podemos optar por evaluar la expresión futura en un proceso R separado asincrónicamente simplemente cambiando la configuración como:
> library( future )
> plan( multisession )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
Con futuros asíncronos, el proceso R actual/principal no bloquea, lo que significa que está disponible para un procesamiento adicional mientras los futuros se resuelven en procesos separados que se ejecutan en segundo plano. En otras palabras, los futuros proporcionan una construcción simple pero poderosa para el procesamiento paralelo y / o distribuido en R.
Ahora, si no puede molestarse en leer todos los detalles de los futuros, pero solo quiere probarlos, salte al final para jugar con la demostración de Mandelbrot utilizando tanto la evaluación paralela como no paralela.
Los futuros se pueden crear implícita o explícitamente . En el ejemplo introductorio anterior, utilizamos futuros implícitos creados a través de la construcción v %<-% { expr }
. Una alternativa es los futuros explícitos utilizando las construcciones f <- future({ expr })
y v <- value(f)
. Con estos, nuestro ejemplo podría escribirse alternativamente como:
> library( future )
> f <- future({
+ cat( " Hello world! n " )
+ 3.14
+ })
> v <- value( f )
Hello world !
> v
[ 1 ] 3.14
Cualquiera de los estilo de la construcción futura funciona igualmente (*) bien. El estilo implícito es más similar a cómo se escribe el código R regular. En principio, todo lo que tiene que hacer es reemplazar <-
con un %<-%
para convertir la tarea en una tarea futura. Por otro lado, esta simplicidad también puede ser engañosa, particularmente cuando se utilizan futuros asíncronos. En contraste, el estilo explícito deja mucho más claro que los futuros se están utilizando, lo que reduce el riesgo de errores y comunica mejor el diseño a otros que leen su código.
(*) Hay casos en que %<-%
no se puede usar sin algunas modificaciones (pequeñas). Volveremos a esto en la sección 'restricciones al usar futuros implícitos' cerca del final de este documento.
Para resumir, para futuros explícitos, usamos:
f <- future({ expr })
- crea un futurov <- value(f)
- Obtiene el valor del futuro (bloquea si aún no se resuelve)Para futuros implícitos, usamos:
v %<-% { expr }
- crea un futuro y una promesa a su valorPara mantenerlo simple, usaremos el estilo implícito en el resto de este documento, pero todo lo discutido también se aplicará a futuros explícitos.
El paquete futuro implementa los siguientes tipos de futuros:
Nombre | OSE | Descripción |
---|---|---|
sincrónico: | No paralelo: | |
sequential | todo | secuencialmente y en el proceso R actual |
asincrónico: | paralelo : | |
multisession | todo | Sesiones R de fondo (en la máquina actual) |
multicore | no Windows/no rstudio | Procesos R bifurcados (en la máquina actual) |
cluster | todo | sesiones R externos en máquinas actuales, locales y/o remotas |
El paquete futuro está diseñado de tal manera que también se puedan implementar estrategias adicionales. Por ejemplo, el paquete Future.Callr proporciona backends futuros que evalúan los futuros en un proceso R de fondo que utiliza el paquete Callr: funcionan de manera similar a los futuros multisession
pero tiene algunas ventajas. Continuando, el paquete Future.BatchTools proporciona futuros para todo tipo de funciones de clúster ("Backends") que es compatible con el paquete BatchTools. Específicamente, también están disponibles futuros para evaluar las expresiones R a través de programadores de trabajo como SLURM, Torque/PBS, Oracle/Sun Grid Engine (SGE) e Instalación de intercambio de carga (LSF).
Por defecto, las expresiones futuras se evalúan con entusiasmo (= instantáneamente) y sincrónicamente (en la sesión R actual). Esta estrategia de evaluación se conoce como "secuencial". En esta sección, revisaremos cada una de estas estrategias y discutiremos lo que tienen en común y cómo difieren.
Antes de pasar por cada una de las diferentes estrategias futuras, probablemente sea útil aclarar los objetivos de la API futura (según lo definido por el paquete futuro). Al programar con futuros, realmente no debería importar qué estrategia futura se usa para ejecutar código. Esto se debe a que realmente no podemos saber a qué recursos computacionales tiene acceso el usuario, por lo que la elección de la estrategia de evaluación debe estar en manos del usuario y no del desarrollador. En otras palabras, el código no debe hacer suposiciones sobre el tipo de futuro utilizado, por ejemplo, sincrónico o asincrónico.
Uno de los diseños de la API futura era encapsular cualquier diferencia de modo que todos los tipos de futuros parezcan funcionar de la misma manera. Esto a pesar de las expresiones puede evaluarse localmente en la sesión R actual o en todo el mundo en sesiones R remotas. Otra ventaja obvia de tener una API y un comportamiento consistentes entre los diferentes tipos de futuros es que ayuda mientras se crea prototipos. Por lo general, uno usaría una evaluación secuencial mientras se desarrolla un script y, más tarde, cuando el script está completamente desarrollado, uno puede activar el procesamiento asíncrono.
Debido a esto, los valores predeterminados de las diferentes estrategias son tales que los resultados y los efectos secundarios de evaluar una expresión futura son lo más similar posible. Más específicamente, lo siguiente es cierto para todos los futuros:
Toda la evaluación se realiza en un entorno local (es decir, local({ expr })
) para que las tareas no afecten el entorno de llamadas. Esto es natural cuando se evalúa en un proceso R externo, pero también se aplica cuando se evalúa en la sesión R actual.
Cuando se construye un futuro, se identifican las variables globales . Para la evaluación asincrónica, los globales se exportan al proceso/sesión R que evaluará la expresión futura. Para futuros secuenciales con evaluación perezosa ( lazy = TRUE
), los globales están "congelados" (clonados a un entorno local del futuro). Además, para proteger contra la exportación de objetos demasiado grandes por error, existe una afirmación incorporada de que el tamaño total de todos los globales es menor que un umbral dado (controlable a través de una opción, cf. help("future.options")
). Si se excede el umbral, se lanza un error informativo.
Las expresiones futuras solo se evalúan una vez . Tan pronto como se haya recopilado el valor (o un error), estará disponible para todas las solicitudes sucesivas.
Aquí hay un ejemplo que ilustra que todas las tareas se realizan a un entorno local:
> plan( sequential )
> a <- 1
> x % <- % {
+ a <- 2
+ 2 * a
+ }
> x
[ 1 ] 4
> a
[ 1 ] 1
Ahora estamos listos para explorar las diferentes estrategias futuras.
Los futuros sincrónicos se resuelven uno tras otro y más comúnmente por el proceso R que los crea. Cuando se resuelve un futuro sincrónico, bloquea el proceso principal hasta que se resuelve.
Los futuros secuenciales son el valor predeterminado a menos que se especifique lo contrario. Fueron diseñados para comportarse lo más similar de la evaluación R regular y al mismo tiempo cumplir con la futura API y sus comportamientos. Aquí hay un ejemplo que ilustra sus propiedades:
> 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
Dado que se está realizando una evaluación secuencial ansiosa, cada uno de los tres futuros se resuelve instantáneamente en el momento en que se crea. Tenga en cuenta también cómo pid
en el entorno de llamadas, al que se le asignó el ID de proceso del proceso actual, no está sobrescribido ni eliminado. Esto se debe a que los futuros se evalúan en un entorno local. Dado que se utiliza el procesamiento síncrono (uni-), el futuro b
Future B se resuelve mediante el proceso R principal (aún en un entorno local), por lo que el valor de b
y pid
es el mismo.
A continuación, recurriremos a futuros asíncronos, que son futuros que se resuelven en segundo plano. Por diseño, estos futuros no están bloqueados, es decir, después de ser creado, el proceso de llamada está disponible para otras tareas, incluida la creación de futuros adicionales. Es solo cuando el proceso de llamada intenta acceder al valor de un futuro que aún no está resuelto, o tratar de crear otro futuro asincrónico cuando todos los procesos R disponibles están ocupados sirviendo otros futuros, que bloquea.
Comenzamos con futuros multisesión porque son compatibles con todos los sistemas operativos. Se evalúa un futuro multisesión en una sesión R de fondo que se ejecuta en la misma máquina que el proceso de llamada R. Aquí está nuestro ejemplo con la evaluación de múltiplessiones:
> 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
Lo primero que observamos es que los valores de a
, c
y pid
son los mismos que anteriormente. Sin embargo, notamos que b
es diferente de antes. Esto se debe a que el futuro b
se evalúa en un proceso R diferente y, por lo tanto, devuelve una identificación de proceso diferente.
Cuando se utiliza la evaluación de múltiplessiones, el paquete lanza un conjunto de sesiones R en el fondo que servirán a futuros multisesión mediante la evaluación de sus expresiones a medida que se crean. Si todas las sesiones de fondo están ocupadas sirviendo otros futuros, la creación del próximo futuro multisesión se bloquea hasta que una sesión de fondo esté disponible nuevamente. El valor total de los procesos de fondo se decide por el valor de availableCores()
, por ejemplo
> availableCores()
mc.cores
2
Este resultado en particular nos dice que la opción mc.cores
se estableció de tal manera que se nos permite usar en un total de dos (2) procesos, incluido el proceso principal. En otras palabras, con estos ajustes, habrá dos (2) procesos de fondo que sirven a los futuros de múltiplessiones. El availableCores()
también es ágil a diferentes opciones y variables de entorno del sistema. Por ejemplo, si se utilizan los programadores de clúster de cómputo (por ejemplo, torque/PBS y slurm), establecen una variable de entorno específica que especifica el número de núcleos que se asignó a cualquier trabajo dado; availableCores()
también los reconoce. Si no se especifica nada más, se utilizarán todos los núcleos disponibles en la máquina, cf. parallel::detectCores()
. Para obtener más detalles, consulte help("availableCores", package = "parallelly")
.
En los sistemas operativos donde R admite la bifurcación de procesos, que es básicamente todo el sistema operativo, excepto Windows, una alternativa al desove sesiones R en el fondo es desembolsar el proceso R existente. Para usar futuros multinúcleo, cuando se admite, especifique:
plan( multicore )
Al igual que para los futuros multisesión, el número máximo de procesos paralelos que se ejecutan se decidirá con availableCores()
, ya que en ambos casos la evaluación se realiza en la máquina local.
Bifurcar un proceso R puede ser más rápido que trabajar con una sesión R separada en segundo plano. Una razón es que la sobrecarga de exportar grandes globales a la sesión de fondo puede ser mayor que cuando se utiliza la memoria y, por lo tanto, la memoria compartida. Por otro lado, la memoria compartida se lee solo , lo que significa que cualquier modificación a los objetos compartidos de uno de los procesos bifurcados ("trabajadores") causará una copia del sistema operativo. Esto también puede suceder cuando el recolector de basura R se ejecuta en uno de los procesos bifurcados.
Por otro lado, el bifurcación de procesos también se considera inestable en algunos entornos R. Por ejemplo, cuando se ejecuta R desde RSTUDIO Process Boifting puede dar como resultado las sesiones R bloqueadas. Debido a esto, el paquete futuro deshabilita los futuros multinúcleo de forma predeterminada cuando se ejecuta desde rstudio. Consulte help("supportsMulticore")
para obtener más detalles.
Los futuros de clúster evalúan las expresiones en un clúster ad-hoc (según lo implementado por el paquete paralelo). Por ejemplo, suponga que tiene acceso a tres nodos n1
, n2
y n3
, luego puede usarlos para una evaluación asíncrona como:
> 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
Cualquier tipo de grupos que creen parallel::makeCluster()
paralelo para futuros de clúster. Por ejemplo, el clúster anterior se puede configurar explícitamente como:
cl <- parallel :: makeCluster(c( " n1 " , " n2 " , " n3 " ))
plan( cluster , workers = cl )
Además, se considera un buen estilo para cerrar el clúster cl
cuando ya no es necesario, es decir, llamar parallel::stopCluster(cl)
. Sin embargo, se cerrará si el proceso principal termina. Para obtener más información sobre cómo configurar y administrar dichos grupos, consulte help("makeCluster", package = "parallel")
. Clusters creados implícitamente utilizando plan(cluster, workers = hosts)
donde hosts
es un vector de caracteres también se cerrará cuando la sesión R principal termine, o cuando se cambie la estrategia futura, por ejemplo, por llamadas plan(sequential)
.
Tenga en cuenta que con la configuración de autenticación automática (por ejemplo, pares de claves SSH), no hay nada que nos impida usar el mismo enfoque para usar un clúster de máquinas remotas.
Si desea ejecutar varios trabajadores en cada nodo, replique el nombre del nodo tantas veces como el número de trabajadores para ejecutar en ese nodo. Por ejemplo,
> plan(cluster, workers = c(rep("n1", times = 3), "n2", rep("n3", times = 5)))
Dirigirá tres trabajadores en n1
, uno en n2
y cinco en n3
, en un total de nueve trabajadores paralelos.
Hasta ahora hemos discutido lo que se puede denominar "topología plana" de los futuros, es decir, todos los futuros se crean y se asignan al mismo entorno. Sin embargo, no hay nada que nos impida usar una "topología anidada" de futuros, donde un conjunto de futuros puede, a su vez, crear otro conjunto de futuros internamente, etc.
Por ejemplo, aquí hay un ejemplo de dos futuros "principales" ( a
y b
) que utiliza la evaluación multisesión y donde el segundo futuro ( b
) a su vez usa dos futuros internos:
> 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
Al inspeccionar las identificaciones del proceso, vemos que hay en un total de tres procesos diferentes involucrados para resolver el futuro. Existe el proceso R principal (PID 1437557), y hay los dos procesos utilizados por a
(PID 1437804) y b
(PID 1437805). Sin embargo, los dos futuros ( b1
y b2
) que están anidados por b
se evalúan por el mismo proceso R que b
Esto se debe a que los futuros anidados usan evaluación secuencial a menos que se especifique lo contrario. Hay algunas razones para esto, pero la razón principal es que nos protege de desencadenar una gran cantidad de procesos de fondo por error, por ejemplo, a través de llamadas recursivas.
Para especificar un tipo diferente de topología de evaluación , aparte del primer nivel de futuros que se resuelve mediante la evaluación de múltiplesisiones y el segundo nivel mediante evaluación secuencial, podemos proporcionar una lista de estrategias de evaluación para plan()
. Primero, las mismas estrategias de evaluación que las anteriores se pueden especificar explícitamente como:
plan( list ( multisession , sequential ))
De hecho, obtendríamos el mismo comportamiento si lo intentamos con múltiples niveles de evaluaciones multisesión;
> 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
La razón de esto es, también aquí, protegernos de lanzar más procesos de lo que la máquina puede admitir. Internamente, esto se hace configurando mc.cores = 1
de modo que funciones como parallel::mclapply()
recurrirán para ejecutarse secuencialmente. Este es el caso de la evaluación multisesión y multinúcleo.
Continuando, si comenzamos mediante una evaluación secuencial y luego usamos la evaluación multisesión para cualquier futuro anidado, obtenemos:
> 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
que muestran claramente que a
y b
se resuelven en el proceso de llamada (PID 1437557), mientras que los dos futuros anidados ( b1
y b2
) se resuelven en dos procesos R separados (PID 1438017 y 1438016).
Dicho esto, de hecho es posible usar estrategias de evaluación multisesión anidada, si especificamos explícitamente (leída la fuerza ) el número de núcleos disponibles en cada nivel. Para hacer esto, necesitamos "ajustar" la configuración predeterminada, que se puede hacer de la siguiente manera:
> 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
Primero, vemos que tanto a
como b
se resuelven en diferentes procesos (PID 1438105 y 1438106) que el proceso de llamada (PID 1437557). En segundo lugar, los dos futuros anidados ( b1
y b2
) se resuelven en otros dos procesos R (PID 1438211 y 1438212).
Para obtener más detalles sobre cómo trabajar con futuros anidados y diferentes estrategias de evaluación en cada nivel, consulte Vignette 'Futuros en R: Future Topologies'.
Es posible verificar si un futuro se ha resuelto o no sin bloquear. Esto se puede hacer utilizando la función resolved(f)
, que requiere una entrada FUTUR f
explícita como entrada. Si trabajamos con futuros implícitos (como en todos los ejemplos anteriores), podemos usar la función f <- futureOf(a)
para recuperar el futuro explícito de uno implícito. Por ejemplo,
> 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
A veces el futuro no es lo que esperabas. Si se produce un error al evaluar un futuro, el error se propaga y se arroja como un error en el entorno de llamadas cuando se solicita el valor futuro . Por ejemplo, si usamos una evaluación perezosa en un futuro que genera un error, podríamos ver algo como
> 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
El error se lanza cada vez que se solicite el valor, es decir, si intentamos obtener el valor nuevamente, generará el mismo error (y salida):
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
In addition : Warning message :
restarting interrupted promise evaluation
Para ver la última llamada en la pila de llamadas que dio el error, podemos usar la función backtrace()
(*) en el futuro, es decir
> backtrace( a )
[[ 1 ]]
log( a )
(*) El traceback()
comúnmente utilizado no proporciona información relevante en el contexto de futuros. Además, desafortunadamente no es posible ver la lista de llamadas (expresiones evaluadas) que condujeron al error; Solo la llamada que dio el error (esto se debe a una limitación en tryCatch()
utilizada internamente).
Siempre que se evalúe una expresión R de forma asincrónica (en paralelo) o secuencialmente a través de la evaluación perezosa, los objetos globales (también conocidos como "libres") deben identificarse y pasar al evaluador. Deben aprobarse exactamente como estaban en el momento en que se creó el futuro, porque, para la evaluación perezosa, los globales pueden cambiar entre cuándo se crea y cuándo se resuelve. Para el procesamiento asincrónico, la razón por la que los globales deben identificarse es para que puedan exportarse al proceso que evalúa el futuro.
El paquete futuro intenta automatizar estas tareas en la medida de lo posible. Lo hace con la ayuda del paquete Globals, que utiliza la inspección del código estático para identificar variables globales. Si se identifica una variable global, se captura y se pone a disposición del proceso de evaluación. Además, si un global se define en un paquete, entonces ese global no se exporta. En cambio, se asegura de que el paquete correspondiente se adjunte cuando se evalúa el futuro. Esto no solo refleja mejor la configuración de la sesión R principal, sino que también minimiza la necesidad de exportar globales, lo que ahorra no solo la memoria sino también el tiempo y el ancho de banda, especialmente cuando se usa nodos de cómputo remotos.
Finalmente, debe aclararse que identificar globales desde la inspección de código estático solo es un problema desafiante. Siempre habrá casos de esquina en los que falle la identificación automática de globales para que se identifiquen los globales falsos (menos preocupaciones) o que faltan algunos de los globales verdaderos (lo que dará como resultado un error de tiempo de ejecución o posiblemente los resultados incorrectos). Vignette 'Futuros en R: Problemas comunes con soluciones' proporciona ejemplos de casos comunes y explica cómo evitarlos, así como cómo ayudar al paquete a identificar globales o ignorar globales falsamente identificados. Si eso no es suficiente, siempre es posible especificar manualmente las variables globales por sus nombres (por ejemplo, globals = c("a", "slow_sum")
) o como pares de valor de nombre (por ejemplo, globals = list(a = 42, slow_sum = my_sum)
).
Hay una limitación con futuros implícitos que no existen para los explícitos. Debido a que un futuro explícito es como cualquier otro objeto en R, se puede asignar en cualquier lugar/cualquier cosa. Por ejemplo, podemos crear varios de ellos en un bucle y asignarlos a una lista, por ejemplo
> 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
Esto no es posible al usar futuros implícitos. Esto se debe a que el operador de asignación de %<-%
no se puede usar en todos los casos donde se puede usar el operador de asignación regular <-
. Solo se puede utilizar para asignar valores futuros a entornos (incluido el entorno de llamadas) muy parecido a cómo funciona assign(name, value, envir)
. Sin embargo, podemos asignar futuros implícitos a entornos utilizando índices con nombre , por ejemplo
> 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
Aquí as.list(v)
bloquea hasta que se hayan resuelto todos los futuros en el entorno v
Luego, sus valores se recopilan y devuelven como una lista regular.
Si se requieren índices numéricos , se pueden usar entornos de lista . Los entornos de lista, que implementan el paquete Listenv, son entornos regulares con operadores de subsistentes personalizados que hacen posible indexarlos de manera muy similar a cómo se pueden indexar las listas. Mediante el uso de entornos de lista donde de otro modo usaríamos listas, también podemos asignar futuros implícitos a objetos similares a la lista utilizando índices numéricos. Por ejemplo,
> 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
Como anteriormente, as.list(v)
bloquea hasta que se resuelvan todos los futuros.
Para ver una ilustración en vivo de cómo se evalúan los diferentes tipos de futuros, ejecute la demostración de Mandelbrot de este paquete. Primero, intente con la evaluación secuencial,
library( future )
plan( sequential )
demo( " mandelbrot " , package = " future " , ask = FALSE )
que se asemeja a cómo se ejecutaría el script si no se utilizaran futuros. Luego, intente la evaluación de múltiplessiones, que calcula los diferentes planos de Mandelbrot utilizando procesos R paralelos que se ejecutan en segundo plano. Intentar,
plan( multisession )
demo( " mandelbrot " , package = " future " , ask = FALSE )
Finalmente, si tiene acceso a múltiples máquinas, puede intentar configurar un clúster de trabajadores y usarlas, por ejemplo,
plan( cluster , workers = c( " n2 " , " n5 " , " n6 " , " n6 " , " n9 " ))
demo( " mandelbrot " , package = " future " , ask = FALSE )
R Package Future está disponible en CRAN y se puede instalar en R como:
install.packages( " future " )
Para instalar la versión previa a la liberación que está disponible en la rama GIT, develop
en GitHub, use:
remotes :: install_github( " futureverse/future " , ref = " develop " )
Esto instalará el paquete desde la fuente.
Para contribuir a este paquete, consulte Contriping.md.