parallel::mclapply() 添加或删除对全局环境的绑定。哪个?
parallel::mclapply() adds or removes bindings to the global environment. Which ones?
为什么这很重要
对于drake
, I want users to be able to execute mclapply()
calls within a locked global environment. The environment is locked for the sake of reproducibility. Without locking, data analysis pipelines could invalidate themselves。
mclapply()
添加或删除全局绑定的证据
set.seed(0)
a <- 1
# Works as expected.
rnorm(1)
#> [1] 1.262954
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2)
# No new bindings allowed.
lockEnvironment(globalenv())
# With a locked environment
a <- 2 # Existing bindings are not locked.
b <- 2 # As expected, we cannot create new bindings.
#> Error in eval(expr, envir, enclos): cannot add bindings to a locked environment
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2) # Unexpected error.
#> Warning in parallel::mclapply(1:2, identity, mc.cores = 2): all scheduled
#> cores encountered errors in user code
由 reprex package (v0.2.1)
创建于 2019-01-16
编辑
关于最初的激励问题,参见https://github.com/ropensci/drake/issues/675 and https://ropenscilabs.github.io/drake-manual/hpc.html#parallel-computing-within-targets。
我认为 parallel:::mc.set.stream()
有答案。显然,mclapply()
默认尝试从全局环境中删除 .Random.seed
。由于默认的 RNG 算法是 Mersenne Twister,我们深入研究下面的 else
块。
> parallel:::mc.set.stream
function ()
{
if (RNGkind()[1L] == "L'Ecuyer-CMRG") {
assign(".Random.seed", get("LEcuyer.seed", envir = RNGenv),
envir = .GlobalEnv)
}
else {
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE))
rm(".Random.seed", envir = .GlobalEnv, inherits = FALSE)
}
}
<bytecode: 0x4709808>
<environment: namespace:parallel>
我们可以使用 mc.set.seed = FALSE
让下面的代码工作,但这在实践中可能不是一个好主意。
set.seed(0)
lockEnvironment(globalenv())
parallel::mclapply(1:2, identity, mc.cores = 2, mc.set.seed = FALSE)
我想知道是否有一种方法可以锁定环境,同时仍然允许我们删除 .Random.seed
。
您可以在锁定环境之前自行删除 .Random.seed
。您还需要加载库(或使用之前的函数)并将 tmp
分配给某些东西。
library(parallel)
tmp <- NULL
rm(".Random.seed", envir = .GlobalEnv, inherits = FALSE)
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2)
当然,这将不允许需要 .Random.seed
的功能(如 rnorm
工作。
解决方法是将 RNG 类型更改为 "L'Ecuyer-CMRG",另请参阅此处 ?nextRNGStream
:
library(parallel)
tmp <- NULL
RNGkind("L'Ecuyer-CMRG")
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, rnorm, mc.cores = 2)
编辑
我想到了解决您问题的另一种方法,我认为这适用于任何 RNG(没有进行太多测试)。您可以使用仅将其设置为 NULL
的函数覆盖删除 .Random.seed
的函数
library(parallel)
mc.set.stream <- function () {
if (RNGkind()[1L] == "L'Ecuyer-CMRG") {
assign(".Random.seed", get("LEcuyer.seed", envir = RNGenv),
envir = .GlobalEnv)
} else {
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
assign(".Random.seed", NULL, envir = .GlobalEnv)
}
}
}
assignInNamespace("mc.set.stream", mc.set.stream, asNamespace("parallel"))
tmp <- NULL
set.seed(0)
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, rnorm, mc.cores = 2)
最后一个想法:您可以创建一个包含所有您不想更改的内容的新环境,将其锁定并在其中工作。
为什么这很重要
对于drake
, I want users to be able to execute mclapply()
calls within a locked global environment. The environment is locked for the sake of reproducibility. Without locking, data analysis pipelines could invalidate themselves。
mclapply()
添加或删除全局绑定的证据
set.seed(0)
a <- 1
# Works as expected.
rnorm(1)
#> [1] 1.262954
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2)
# No new bindings allowed.
lockEnvironment(globalenv())
# With a locked environment
a <- 2 # Existing bindings are not locked.
b <- 2 # As expected, we cannot create new bindings.
#> Error in eval(expr, envir, enclos): cannot add bindings to a locked environment
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2) # Unexpected error.
#> Warning in parallel::mclapply(1:2, identity, mc.cores = 2): all scheduled
#> cores encountered errors in user code
由 reprex package (v0.2.1)
创建于 2019-01-16编辑
关于最初的激励问题,参见https://github.com/ropensci/drake/issues/675 and https://ropenscilabs.github.io/drake-manual/hpc.html#parallel-computing-within-targets。
我认为 parallel:::mc.set.stream()
有答案。显然,mclapply()
默认尝试从全局环境中删除 .Random.seed
。由于默认的 RNG 算法是 Mersenne Twister,我们深入研究下面的 else
块。
> parallel:::mc.set.stream
function ()
{
if (RNGkind()[1L] == "L'Ecuyer-CMRG") {
assign(".Random.seed", get("LEcuyer.seed", envir = RNGenv),
envir = .GlobalEnv)
}
else {
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE))
rm(".Random.seed", envir = .GlobalEnv, inherits = FALSE)
}
}
<bytecode: 0x4709808>
<environment: namespace:parallel>
我们可以使用 mc.set.seed = FALSE
让下面的代码工作,但这在实践中可能不是一个好主意。
set.seed(0)
lockEnvironment(globalenv())
parallel::mclapply(1:2, identity, mc.cores = 2, mc.set.seed = FALSE)
我想知道是否有一种方法可以锁定环境,同时仍然允许我们删除 .Random.seed
。
您可以在锁定环境之前自行删除 .Random.seed
。您还需要加载库(或使用之前的函数)并将 tmp
分配给某些东西。
library(parallel)
tmp <- NULL
rm(".Random.seed", envir = .GlobalEnv, inherits = FALSE)
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, identity, mc.cores = 2)
当然,这将不允许需要 .Random.seed
的功能(如 rnorm
工作。
解决方法是将 RNG 类型更改为 "L'Ecuyer-CMRG",另请参阅此处 ?nextRNGStream
:
library(parallel)
tmp <- NULL
RNGkind("L'Ecuyer-CMRG")
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, rnorm, mc.cores = 2)
编辑
我想到了解决您问题的另一种方法,我认为这适用于任何 RNG(没有进行太多测试)。您可以使用仅将其设置为 NULL
.Random.seed
的函数
library(parallel)
mc.set.stream <- function () {
if (RNGkind()[1L] == "L'Ecuyer-CMRG") {
assign(".Random.seed", get("LEcuyer.seed", envir = RNGenv),
envir = .GlobalEnv)
} else {
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
assign(".Random.seed", NULL, envir = .GlobalEnv)
}
}
}
assignInNamespace("mc.set.stream", mc.set.stream, asNamespace("parallel"))
tmp <- NULL
set.seed(0)
lockEnvironment(globalenv())
tmp <- parallel::mclapply(1:2, rnorm, mc.cores = 2)
最后一个想法:您可以创建一个包含所有您不想更改的内容的新环境,将其锁定并在其中工作。