[英]parallel package very slow initialization when used inside another R package
出于测试目的,在我的 R package 中,我放置了以下 function:
parsetup <- function(){
cl <- parallel::makeCluster(12,type='PSOCK')
parallel::clusterCall(cl,function() 1+1)
}
当我运行mypkg::parsetup()
时,大约需要 6 秒才能完成。 当我运行parsetup2 <- mypkg:parsetup(); parsetup2()
parsetup2 <- mypkg:parsetup(); parsetup2()
在全局环境中,大约需要 6 秒才能完成。 当我在全局环境中运行定义解析 function 的代码,然后运行parsetup()
时,大约需要 0.3s
这对我来说似乎很愚蠢,任何人都可以解释原因和/或提出解决方法吗? 在我想使用并行化的每个 function 中添加 6s 非常令人沮丧。
编辑:在 clusterCall 期间发生时间差异,在每种情况下创建的集群节点数为 12。
sessionInfo()
R version 4.0.4 (2021-02-15)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)
Matrix products: default
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252 LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C LC_TIME=English_United States.1252
system code page: 65001
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ctsem_3.4.3 testthat_3.0.2 profvis_0.3.7 Rcpp_1.0.6
我认为这里有几件事在起作用。 第一个是环境和他们的父母。 根据 function 的复杂性,可能需要将大量数据发送到并行进程。 以这个创建一些闭包的代码为例:
cl <- parallel::makeCluster(2L)
fun_factory <- function(x) { function() { list(x = x, addr = lobstr::obj_addr(x)) } }
fun <- fun_factory(0)
lobstr::obj_addr(environment(fun)$x)
# [1] "0x557ad9605068"
str(parallel::clusterCall(cl, fun))
# List of 2
# $ :List of 2
# ..$ x : num 0
# ..$ addr: chr "0x564b16e37e68"
# $ :List of 2
# ..$ x : num 0
# ..$ addr: chr "0x55a5a9437e68"
如您所见, function 被“包含”在一个包含x
的环境中,当 function 被发送给工作人员进行评估时,该环境需要“复制”,这会导致x
的副本具有不同的 ZCDE18D7B49357F06
您看不到与 clusterEvalQ 完全相同的行为,因为clusterEvalQ
仅序列化代码表达式(另请参见?base::quote
),因此在此示例中它不会直接工作:
#parallel::clusterExport(cl, "fun") # uncomment this to make it work
parallel::clusterEvalQ(cl, fun())
Error in checkForRemoteErrors(lapply(cl, recvResult)) :
2 nodes produced errors; first error: could not find function "fun"
包内定义的函数有一些 “围绕”它们的环境。 我怀疑所有内容都会被序列化以供工人进行评估,但我并不感到惊讶它并非可以忽略不计。 另外,当你在worker中执行需要其他包的函数时,每个worker都必须加载package来执行,所以你不想一直重新创建cl
。
一些开销是不可避免的,但为了避免一直重新创建“集群”,您将云管理的责任交给用户,对于 package 开发人员来说,这可能会简化一些事情,因为您不必担心调用parallel::stopCluster
。 我个人喜欢foreach
中的抽象。 在您的 package 中,您可以定义如下函数:
my_par_fun <- function(x) {
foreach::foreach(x = x) %dopar% {
x + 1
}
}
如果没有注册并行后端,代码将按顺序执行。 如果用户想要并行化,他们可以安装像doParallel
这样的后端包并调用类似的东西
cl <- parallel::makeCluster(2L)
doParallel::registerDoParallel(cl)
在调用你的包的函数之前。 完成后,他们可以调用parallel::stopCluster
和foreach::registerDoSEQ
,这对您的 package 代码保持透明。
仅供参考,在使用foreach
时,您不需要使用数据,您可以执行以下操作:
my_par_fun <- function(...) {
foreach::foreach(i = 1L:foreach::getDoParWorkers()) %dopar% {
# a very time-consuming task
}
}
这样,每个工作人员都会获得一项任务,而不管用户创建了多少。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.