O objetivo do pacote futuro é fornecer uma maneira muito simples e uniforme de avaliar as expressões R de forma assíncrona usando vários recursos disponíveis para o usuário.
Na programação, um futuro é uma abstração para um valor que pode estar disponível em algum momento no futuro. O estado de um futuro pode ser resolvido ou resolvido . Assim que for resolvido, o valor está disponível instantaneamente. Se o valor for consultado enquanto o futuro ainda não estiver resolvido, o processo atual será bloqueado até que o futuro seja resolvido. É possível verificar se um futuro é resolvido ou não sem bloquear. Exatamente como e quando os futuros são resolvidos depende de qual estratégia é usada para avaliá -los. Por exemplo, um futuro pode ser resolvido usando uma estratégia seqüencial, o que significa que ela é resolvida na sessão R atual. Outras estratégias podem ser resolver futuros de forma assíncrona, por exemplo, avaliando expressões em paralelo na máquina atual ou simultaneamente em um cluster de computação.
Aqui está um exemplo que ilustra como o básico do futuro funciona. Primeiro, considere o seguinte snippet de código que usa código R simples:
> v <- {
+ cat( " Hello world! n " )
+ 3.14
+ }
Hello world !
> v
[ 1 ] 3.14
Funciona atribuindo o valor de uma expressão à variável v
e, em seguida, imprimimos o valor de v
. Além disso, quando a expressão para v
é avaliada, também imprimimos uma mensagem.
Aqui está o mesmo snippet de código modificado para usar futuros:
> library( future )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
A diferença está em como v
é construído; Com r simples, usamos <-
enquanto com os futuros usamos %<-%
. A outra diferença é que a saída é retransmitida após a resolução do futuro (não durante) e quando o valor é consultado (consulte a Vignette 'Saída de texto').
Então, por que os futuros são úteis? Porque podemos optar por avaliar a expressão futura em um processo R separado de forma assíncrona, simplesmente alternando as configurações como:
> library( future )
> plan( multisession )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
Com os futuros assíncronos, o processo R atual/principal não bloqueia, o que significa que está disponível para processamento adicional enquanto os futuros estão sendo resolvidos em processos separados em execução em segundo plano. Em outras palavras, os futuros fornecem uma construção simples, mas poderosa, para processamento paralelo e / ou distribuído em R.
Agora, se você não se incomoda em ler todos os detalhes da questão sobre os futuros, mas só quer experimentá-los, pule até o final para brincar com a demonstração de Mandelbrot usando avaliação paralela e não paralela.
Os futuros podem ser criados implicitamente ou explicitamente . No exemplo introdutório acima, usamos futuros implícitos criados através do construto v %<-% { expr }
. Uma alternativa são futuros explícitos usando as construções f <- future({ expr })
e v <- value(f)
. Com isso, nosso exemplo pode ser escrito como:
> library( future )
> f <- future({
+ cat( " Hello world! n " )
+ 3.14
+ })
> v <- value( f )
Hello world !
> v
[ 1 ] 3.14
Qualquer estilo de construção futura funciona igualmente (*) bem. O estilo implícito é mais semelhante ao código R regular é escrito. Em princípio, tudo o que você precisa fazer é substituir <-
por um %<-%
para transformar a tarefa em uma tarefa futura. Por outro lado, essa simplicidade também pode enganar, principalmente quando os futuros assíncronos estão sendo usados. Por outro lado, o estilo explícito torna muito mais claro que os futuros estão sendo usados, o que reduz o risco de erros e comunica melhor o design a outras pessoas que lêem seu código.
(*) Há casos em que %<-%
não podem ser usados sem algumas (pequenas) modificações. Voltaremos a isso na seção 'Restrições ao usar futuros implícitos' perto do final deste documento.
Para resumir, para futuros explícitos, usamos:
f <- future({ expr })
- cria um futurov <- value(f)
- obtém o valor do futuro (blocos se ainda não foram resolvidos)Para futuros implícitos, usamos:
v %<-% { expr }
- cria um futuro e uma promessa ao seu valorPara simplificar, usaremos o estilo implícito no restante deste documento, mas tudo discutido também se aplicará a futuros explícitos.
O pacote futuro implementa os seguintes tipos de futuros:
Nome | Oses | Descrição |
---|---|---|
síncrono: | Não paralelo: | |
sequential | todos | sequencialmente e no processo R atual |
assíncrono: | paralelo : | |
multisession | todos | Background R Sessions (na máquina atual) |
multicore | Não Windows/Not rstudio | Processos R bifurcados (na máquina atual) |
cluster | todos | sessões R externas em máquinas atuais, locais e/ou remotas |
O pacote futuro foi projetado de modo que o suporte a estratégias adicionais também possa ser implementado. Por exemplo, o pacote Future.Callr fornece backnds futuros que avaliam futuros em um processo R de segundo plano, utilizando o pacote Callr - eles funcionam de maneira semelhante aos futuros multisession
, mas têm algumas vantagens. Continuando, o pacote Future. Especificamente, também estão disponíveis futuros para avaliar expressões de R por meio de agendadores de empregos, como Slurm, Torque/PBS, Oracle/Sun Grid Engine (SGE) e Facility Compartilhamento de Carga (LSF).
Por padrão, expressões futuras são avaliadas ansiosamente (= instantaneamente) e de síncrona (na sessão R atual). Essa estratégia de avaliação é chamada de "sequencial". Nesta seção, examinaremos cada uma dessas estratégias e discutiremos o que elas têm em comum e como elas diferem.
Antes de passar por cada uma das diferentes estratégias futuras, provavelmente é útil esclarecer os objetivos da API futura (conforme definido pelo pacote futuro). Ao programar com futuros, não deve realmente importar qual estratégia futura é usada para executar o código. Isso ocorre porque não podemos realmente saber quais recursos computacionais a que o usuário tem acesso, para que a escolha da estratégia de avaliação deve estar nas mãos do usuário e não do desenvolvedor. Em outras palavras, o código não deve fazer suposições sobre o tipo de futuro usado, por exemplo, síncrono ou assíncrono.
Um dos projetos da API futura foi encapsular quaisquer diferenças, de modo que todos os tipos de futuros pareçam funcionar da mesma forma. Isso apesar das expressões pode ser avaliado localmente na atual sessão de R ou em todo o mundo em sessões remotas. Outra vantagem óbvia de ter uma API e um comportamento consistente entre diferentes tipos de futuros é que ela ajuda durante a prototipagem. Normalmente, se usaria avaliação seqüencial durante a criação de um script e, posteriormente, quando o script é totalmente desenvolvido, pode -se ativar o processamento assíncrono.
Por esse motivo, os padrões das diferentes estratégias são tais que os resultados e os efeitos colaterais da avaliação de uma expressão futura são o mais semelhante possível. Mais especificamente, o seguinte é verdadeiro para todos os futuros:
Toda a avaliação é feita em um ambiente local (ou seja, local({ expr })
), para que as atribuições não afetem o ambiente de chamada. Isso é natural ao avaliar em um processo R externo, mas também é aplicado ao avaliar a sessão R atual.
Quando um futuro é construído, as variáveis globais são identificadas . Para avaliação assíncrona, os globais são exportados para o processo/sessão que avaliará a expressão futura. Para futuros seqüenciais com avaliação preguiçosa ( lazy = TRUE
), os globais são "congelados" (clonados para um ambiente local do futuro). Além disso, a fim de proteger contra a exportação de objetos muito grandes por engano, há uma afirmação interna de que o tamanho total de todos os globais é menor que um determinado limite (controlável por meio de uma opção, cf. help("future.options")
). Se o limite for excedido, um erro informativo será lançado.
Expressões futuras são avaliadas apenas uma vez . Assim que o valor (ou um erro) for coletado, ele estará disponível para todas as solicitações seguintes.
Aqui está um exemplo que ilustra que todas as tarefas são feitas a um ambiente local:
> plan( sequential )
> a <- 1
> x % <- % {
+ a <- 2
+ 2 * a
+ }
> x
[ 1 ] 4
> a
[ 1 ] 1
Agora estamos prontos para explorar as diferentes estratégias futuras.
Os futuros síncronos são resolvidos um após o outro e, mais comumente, pelo processo R que os cria. Quando um futuro síncrono está sendo resolvido, ele bloqueia o processo principal até resolver.
Os futuros seqüenciais são o padrão, a menos que especificado de outra forma. Eles foram projetados para se comportar o mais semelhante possível à avaliação regular de R, enquanto ainda cumpriam a API futura e seus comportamentos. Aqui está um exemplo ilustrando suas propriedades:
> 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
Como está ocorrendo uma avaliação sequencial ansiosa, cada um dos três futuros é resolvido instantaneamente no momento em que é criado. Observe também como pid
no ambiente de chamada, que recebeu o ID do processo do processo atual, não é substituído nem removido. Isso ocorre porque os futuros são avaliados em um ambiente local. Como o processamento síncrono (uni-) é usado, o futuro b
é resolvido pelo processo R principal (ainda em um ambiente local), e é por isso que o valor de b
e pid
é o mesmo.
Em seguida, recorrer a futuros assíncronos, que são futuros que são resolvidos em segundo plano. Por design, esses futuros não são bloqueadores, ou seja, depois de ser criado, o processo de chamada está disponível para outras tarefas, incluindo a criação de futuros adicionais. Somente quando o processo de chamada tenta acessar o valor de um futuro que ainda não foi resolvido, ou tentando criar outro futuro assíncrono quando todos os processos R disponíveis estão ocupados servir a outros futuros, que bloqueia.
Começamos com futuros de multissession porque eles são suportados por todos os sistemas operacionais. Um futuro multissession é avaliado em uma sessão R em segundo plano em execução na mesma máquina que o processo de chamada R. Aqui está o nosso exemplo com avaliação multissession:
> 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
A primeira coisa que observamos é que os valores de a
, c
e pid
são os mesmos que anteriormente. No entanto, notamos que b
é diferente de antes. Isso ocorre porque o futuro b
é avaliado em um processo R diferente e, portanto, retorna um ID de processo diferente.
Quando a avaliação multissession é usada, o pacote lança um conjunto de sessões R em segundo plano que servirão futuros de multissession, avaliando suas expressões à medida que são criadas. Se todas as sessões de segundo plano estiverem ocupadas servindo outros futuros, a criação do próximo futuro multissession será bloqueada até que uma sessão de segundo plano fique disponível novamente. O número total de processos em segundo plano lançado é decidido pelo valor de availableCores()
, por exemplo
> availableCores()
mc.cores
2
Esse resultado específico nos diz que a opção mc.cores
foi definida de modo que podemos usar no total de dois (2) processos, incluindo o processo principal. Em outras palavras, com essas configurações, haverá dois (2) processos de fundo que servem os futuros de multisession. O availableCores()
também é ágil a diferentes opções e variáveis de ambiente do sistema. Por exemplo, se os agendadores de cluster de computação forem usados (por exemplo, torque/PBS e SLURM), eles definem variável de ambiente específica, especificando o número de núcleos que foram atribuídos a um determinado trabalho; availableCores()
também os reconhece. Se nada mais for especificado, todos os núcleos disponíveis na máquina serão utilizados, cf. parallel::detectCores()
. Para mais detalhes, consulte help("availableCores", package = "parallelly")
.
Nos sistemas operacionais em que R suporta a forquilha de processos, que é basicamente todo sistema operacional, exceto o Windows, uma alternativa para desova sessões de R em segundo plano é forçar o processo R existente. Para usar futuros multicore, quando suportados, especifique:
plan( multicore )
Assim como nos futuros multisession, o número máximo de processos paralelos em execução será decidido pelo availableCores()
, pois em ambos os casos a avaliação é feita na máquina local.
A forquilha de um processo R pode ser mais rápida do que trabalhar com uma sessão R separada em execução em segundo plano. Uma razão é que a sobrecarga da exportação de grandes globais para a sessão em segundo plano pode ser maior do que quando for esgotada e, portanto, a memória compartilhada é usada. Por outro lado, a memória compartilhada é somente leitura , o que significa que quaisquer modificações em objetos compartilhados por um dos processos bifurcados ("trabalhadores") causarão uma cópia pelo sistema operacional. Isso também pode acontecer quando o coletor de lixo R é executado em um dos processos bifurcados.
Por outro lado, o forking de processos também é considerado instável em alguns ambientes R. Por exemplo, ao executar o R de dentro do RSTUDIO Process Forking pode resultar em sessões R com travamento. Por esse motivo, o pacote futuro desativa os futuros multicore por padrão ao correr do RStudio. Consulte help("supportsMulticore")
para obter mais detalhes.
Os futuros de cluster avaliam expressões em um cluster ad-hoc (conforme implementado pelo pacote paralelo). Por exemplo, suponha que você tenha acesso a três nós n1
, n2
e n3
, você pode usá -los para avaliação assí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
Quaisquer tipos de clusters que parallel::makeCluster()
criar podem ser usados para futuros de cluster. Por exemplo, o cluster acima pode ser explicitamente configurado como:
cl <- parallel :: makeCluster(c( " n1 " , " n2 " , " n3 " ))
plan( cluster , workers = cl )
Além disso, é considerado um bom estilo desligar o cluster cl
quando não é mais necessário, ou seja, chamando parallel::stopCluster(cl)
. No entanto, ele se desligará se o processo principal for encerrado. Para obter mais informações sobre como configurar e gerenciar esses clusters, consulte help("makeCluster", package = "parallel")
. Os clusters criaram implicitamente usando plan(cluster, workers = hosts)
onde hosts
é um vetor de caractere também serão fechados quando a sessão R principal terminar, ou quando a estratégia futura for alterada, por exemplo, chamando plan(sequential)
.
Observe que, com a configuração automática de autenticação (por exemplo, pares de chaves ssh), não há nada que nos impeça de usar a mesma abordagem para usar um cluster de máquinas remotas.
Se você deseja executar vários trabalhadores em cada nó, replique o nome do nó quantas vezes o número de trabalhadores para executar nesse nó. Por exemplo,
> plan(cluster, workers = c(rep("n1", times = 3), "n2", rep("n3", times = 5)))
administrará três trabalhadores no n1
, um no n2
e cinco no n3
, no total de nove trabalhadores paralelos.
Até onde discutimos o que pode ser chamado de "topologia plana" de futuros, ou seja, todos os futuros são criados e designados para o mesmo ambiente. No entanto, não há nada que nos impeça de usar uma "topologia aninhada" de futuros, onde um conjunto de futuros pode, por sua vez, criar outro conjunto de futuros internamente e assim por diante.
Por exemplo, aqui está um exemplo de dois futuros "top" ( a
e b
) que usa avaliação de várias estações e onde o segundo futuro ( b
), por sua vez, usa dois 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
Por inspeção dos IDs do processo, vemos que existem no total três processos diferentes envolvidos para resolver o futuro. Existe o processo R principal (PID 1437557) e há os dois processos usados por a
(PID 1437804) e b
(PID 1437805). No entanto, os dois futuros ( b1
e b2
) que são aninhados por b
são avaliados pelo mesmo processo R que b
Isso ocorre porque os futuros aninhados usam avaliação seqüencial, a menos que especificado de outra forma. Existem algumas razões para isso, mas o principal motivo é que ela nos protege de gerar um grande número de processos de fundo por engano, por exemplo, por meio de chamadas recursivas.
Para especificar um tipo diferente de topologia de avaliação , além do primeiro nível de futuros que estão sendo resolvidos pela avaliação multissess e pelo segundo nível por avaliação seqüencial, podemos fornecer uma lista de estratégias de avaliação para plan()
. Primeiro, as mesmas estratégias de avaliação acima podem ser explicitamente especificadas como:
plan( list ( multisession , sequential ))
Na verdade, teríamos o mesmo comportamento se tentarmos com vários níveis de avaliações multissess;
> 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
A razão para isso é, também aqui, para nos proteger de lançar mais processos do que a máquina pode suportar. Internamente, isso é feito definindo mc.cores = 1
, de modo que funções como parallel::mclapply()
cairão para executar sequencialmente. É o caso da avaliação multissession e multicore.
Continuando, se começarmos por avaliação seqüencial e depois usarmos avaliação multissession para qualquer futuro aninhado, obtemos:
> 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 mostram claramente que a
e b
são resolvidos no processo de chamada (PID 1437557), enquanto os dois futuros aninhados ( b1
e b2
) são resolvidos em dois processos R separados (PIDS 1438017 e 1438016).
Dito isto, é de fato possível usar estratégias de avaliação de múltiplas estações aninhadas, se especificarmos explicitamente ( Força de leitura) o número de núcleos disponíveis em cada nível. Para fazer isso, precisamos "ajustar" as configurações padrão, o que pode ser feito da seguinte forma:
> 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
Primeiro, vemos que a
e b
são resolvidos em diferentes processos (PIDs 1438105 e 1438106) do processo de chamada (PID 1437557). Segundo, os dois futuros aninhados ( b1
e b2
) são resolvidos em outros dois processos R (PIDs 1438211 e 1438212).
Para obter mais detalhes sobre o trabalho com futuros aninhados e diferentes estratégias de avaliação em cada nível, consulte a Vignette 'Futures em R: futuras topologias'.
É possível verificar se um futuro foi resolvido ou não sem bloquear. Isso pode ser feito usando a função resolved(f)
, que leva um futuro explícito f
como entrada. Se trabalharmos com futuros implícitos (como em todos os exemplos acima), podemos usar a função f <- futureOf(a)
para recuperar o futuro explícito de uma implícita. Por exemplo,
> 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
Às vezes, o futuro não é o que você esperava. Se ocorrer um erro ao avaliar um futuro, o erro será propagado e lançado como um erro no ambiente de chamada quando o valor futuro é solicitado . Por exemplo, se usarmos uma avaliação preguiçosa em um futuro que gera um erro, podemos 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
O erro é lançado cada vez que o valor é solicitado, ou seja, se tentarmos obter o valor novamente gerará o mesmo erro (e saída):
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
In addition : Warning message :
restarting interrupted promise evaluation
Para ver a última chamada na pilha de chamadas que deu o erro, podemos usar a função backtrace()
(*) no futuro, ou seja,
> backtrace( a )
[[ 1 ]]
log( a )
(*) O traceback()
comumente usado não fornece informações relevantes no contexto do futuro. Além disso, infelizmente não é possível ver a lista de chamadas (expressões avaliadas) que levaram ao erro; Somente a chamada que deu o erro (isso se deve a uma limitação no tryCatch()
usada internamente).
Sempre que uma expressão R deve ser avaliada de forma assíncrona (em paralelo) ou sequencialmente por meio de avaliação preguiçosa, os objetos globais (também conhecidos como "livre") devem ser identificados e passados para o avaliador. Eles precisam ser aprovados exatamente como estavam no momento em que o futuro foi criado, porque, para avaliação preguiçosa, os globais podem mudar entre quando é criado e quando é resolvido. Para processamento assíncrono, a razão pela qual os globais precisam ser identificados é para que possam ser exportados para o processo que avalia o futuro.
O pacote futuro tenta automatizar essas tarefas o mais longe possível. Faz isso com a ajuda do pacote Globals, que usa inspeção de código estático para identificar variáveis globais. Se uma variável global for identificada, ela será capturada e disponibilizada para o processo de avaliação. Além disso, se um global for definido em um pacote, esse global não será exportado. Em vez disso, é garantido que o pacote correspondente esteja anexado quando o futuro é avaliado. Isso não apenas reflete melhor a configuração da sessão R principal, mas também minimiza a necessidade de exportar globais, o que economiza não apenas a memória, mas também o tempo e a largura de banda, especialmente ao usar nós de computação remota.
Finalmente, deve -se esclarecer que a identificação de globais apenas da inspeção estática de código é um problema desafiador. Sempre haverá casos de canto em que a identificação automática de globais falhe para que os falsos globais sejam identificados (menos preocupantes) ou alguns dos verdadeiros globais estão faltando (o que resultará em um erro em tempo de execução ou possivelmente nos resultados errados). Os futuros da Vignette em R: Comuns com soluções 'fornecem exemplos de casos comuns e explica como evitá -los e como ajudar o pacote para identificar globais ou ignorar globais falsamente identificados. Se isso não é suficiente, é sempre possível especificar manualmente as variáveis globais por seus nomes (por exemplo, globals = c("a", "slow_sum")
) ou como pares de nomes-valor (por exemplo, globals = list(a = 42, slow_sum = my_sum)
).
Há uma limitação com futuros implícitos que não existem para os explícitos. Como um futuro explícito é como qualquer outro objeto em r, ele pode ser atribuído em qualquer lugar/a qualquer coisa. Por exemplo, podemos criar vários deles em um loop e atribuí -los a uma lista, por exemplo
> 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
Isso não é possível fazer ao usar futuros implícitos. Isso ocorre porque o operador de cessão %<-%
não pode ser usado em todos os casos em que o operador regular <-
de atribuição pode ser usado. Ele só pode ser usado para atribuir valores futuros aos ambientes (incluindo o ambiente de chamada), como assign(name, value, envir)
funciona. No entanto, podemos atribuir futuros implícitos a ambientes usando índices nomeados , por exemplo,
> 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
Aqui os blocos as.list(v)
até que todos os futuros no ambiente v
sejam resolvidos. Em seguida, seus valores são coletados e devolvidos como uma lista regular.
Se forem necessários índices numéricos , os ambientes de lista poderão ser usados. Os ambientes de lista, que são implementados pelo pacote LOWVV, são ambientes regulares com operadores de subconjuntos personalizados, possibilitando indexá -los como as listas podem ser indexadas. Ao usar ambientes de lista em que usaríamos listas, também podemos atribuir futuros implícitos para listar objetos semelhantes a índices numéricos. Por exemplo,
> 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, os blocos as.list(v)
até que todos os futuros sejam resolvidos.
Para ver uma ilustração ao vivo como diferentes tipos de futuros são avaliados, execute a demonstração de Mandelbrot deste pacote. Primeiro, tente com a avaliação seqüencial,
library( future )
plan( sequential )
demo( " mandelbrot " , package = " future " , ask = FALSE )
que se assemelha a como o script funcionaria se os futuros não fossem usados. Em seguida, tente a avaliação multissession, que calcula os diferentes planos Mandelbrot usando processos R paralelos em execução em segundo plano. Tentar,
plan( multisession )
demo( " mandelbrot " , package = " future " , ask = FALSE )
Finalmente, se você tiver acesso a várias máquinas, pode tentar configurar um conjunto de trabalhadores e usá -los, por exemplo,
plan( cluster , workers = c( " n2 " , " n5 " , " n6 " , " n6 " , " n9 " ))
demo( " mandelbrot " , package = " future " , ask = FALSE )
R Pacote Future está disponível em Cran e pode ser instalado em R como:
install.packages( " future " )
Para instalar a versão de pré-lançamento disponível no Git Branch, develop
no GitHub, use:
remotes :: install_github( " futureverse/future " , ref = " develop " )
Isso instalará o pacote da fonte.
Para contribuir com este pacote, consulte Contribuindo.md.