这可能是一个适合哲学家的...(或@Steve Weston或@Martin Morgan)
在使用parLapply时,我一直遇到内存泄漏的问题,并且在研究了足够多的线程之后,我认为这个问题很合理。我花了一些时间来尝试解决这个问题,尽管我对所观察到的行为为何会发生什么有了一个线索,但我却不知道如何解决它。
将以下内容视为源脚本,另存为:parallel_question.R
rf.parallel<-function(n=10){
library(parallel)
library(randomForest)
rf.form<- as.formula(paste("Final", paste(c('x','y','z'), collapse = "+"), sep = " ~ "))
rf.df<-data.frame(Final=runif(10000),y=runif(10000),x=runif(10000),z=runif(10000))
rf.df.list<-split(rf.df,rep(1:n,nrow(rf.df))[1:nrow(rf.df)])
cl<-makeCluster(n)
rf.list<-parLapply(cl,rf.df.list,function(x,rf.form,n){
randomForest::randomForest(rf.form,x,ntree=100,nodesize=10, norm.votes=FALSE)},rf.form,n)
stopCluster(cl)
return(rf.list)
}
我们通过以下方式获取并运行脚本:
scrip.loc<-"G:\\Scripts_Library\\R\\Stack_Answers\\parallel_question.R"
source(scrip.loc)
rf.parallel(n=10)
公平直截了当地...我们并行运行了几个随机森林。似乎可以提高内存效率。我们可以稍后合并它们,或者做其他事情。便利。真好表现得很好。
现在考虑以下脚本,另存为parallel_question_2.R
rf.parallel_2<-function(n=10){
library(parallel)
library(magrittr)
library(randomForest)
rf.form<- as.formula(paste("Final", paste(c('x','y','z'), collapse = "+"), sep = " ~ "))
rf.df<-data.frame(Final=runif(10000),y=runif(10000),x=runif(10000),z=runif(10000))
large.list<-rep(rf.df,10000)
rf.df.list<-split(rf.df,rep(1:n,nrow(rf.df))[1:nrow(rf.df)])
cl<-makeCluster(n)
rf.list<-parLapply(cl,rf.df.list,function(x,rf.form,n){
randomForest::randomForest(rf.form,x,ntree=100,nodesize=10, norm.votes=FALSE)},rf.form,n)
stopCluster(cl)
return(rf.list)
}
在第二个脚本中,我们在源环境中有大量清单。我们不是在调用列表或将其引入并行函数。我已将列表的大小设置为至少在32GB的计算机上可能是个问题。
scrip.loc<-"G:\\Scripts_Library\\R\\Stack_Answers\\parallel_question_2.R"
source(scrip.loc)
rf.parallel_2(n=10)
当我们运行第二个脚本时,最终会携带大约3gb(大列表的大小)*设置为集群的工作线程数,以及其他内容。如果我们在非源环境中运行第二个脚本的内容,则不是这种情况;相反,我们得到了一个〜3gb的列表,并行化函数运行没有问题,这就是结尾。
因此..工作环境如何/为什么从父环境中获取不必要的变量元素?为什么它仅在源脚本中发生?当我有一个源代码,大而复杂的脚本时,该如何缓解这一问题呢?该脚本的各个子节都是并行的(但可能携带3-10gb的中间数据)?
相关或相似的主题:
答案 0 :(得分:2)
parLapply(cl, X, FUN, ...)
的签名将FUN
应用于X
的每个元素。工作人员需要知道FUN
,因此它将被序列化并发送给工作人员。什么是R函数?它是定义函数的代码,和定义函数的环境。为什么要环境?因为在 R 中,引用在FUN
之外定义的变量是合法的,例如,
f = function(y) x + y
x = 1; f(1)
## [1] 2
第二个复杂度是 R 允许函数更新函数外部的变量
f = function(y) { x <<- x + 1; x + y }
x = 1; f(1)
## [1] 3
在上面,我们可以想象我们可以找出f()
环境的哪些部分(仅变量x
),但是通常这种分析不是可能不需要实际评估函数,例如f = function(y, name) get(name) + y; x = 1; f(1, "x")
因此,要在工作程序上评估FUN
,工作程序需要知道FUN
的定义和环境FUN
的内容已在其中定义。 R 通过使用FUN
使工作人员了解serialize()
。结果很容易看到
f = function(n) { x = sample(n); length(serialize(function() {}, NULL)) }
f(1)
## [1] 754
f(10)
## [1] 1064
f(100)
## [1] 1424
环境中的较大对象会导致向工作人员发送/使用的更多信息。
如果考虑到这一点,那么到目前为止的描述将意味着整个R会话应该序列化到工作程序(或磁盘,如果使用serialize()
来保存对象) )-f()
中隐式函数的环境包括f()
的主体,还包括f()
的环境(即全局环境)和全局环境,这是搜索路径...(检出environment(f)
和parent.env(.GlobalEnv)
)。 R 有一个任意规则,即它停止在全局环境中。因此,不要使用隐式的function() {}
,而应在.GlobalEnv
g = function() {}
f = function(n) { x = sample(n); length(serialize(g, NULL)) }
f(1)
## [1] 592
f(1000)
## [1] 592
还请注意,这会对可以序列化哪些功能产生影响。例如,如果g()
在下面的代码中序列化,它将“知道” x
f = function(y) { x = 1; g = function(y) x + y; g() }
f(1)
## [1] 2
但是这里并没有-它知道在其定义的环境中的符号,但是不知道从其调用环境中的符号。
rm(x)
g = function(y) x + y
f = function(y) { x = 1; g() }
f()
## Error in g() : object 'x' not found
您可以在脚本中进行比较
cl = makeCluster(2)
f = function(n) {
x = sample(n)
parLapply(
cl, 1,
function(...)
length(serialize(environment(), NULL))
)
}
f(1)[[1]]
## [1] 256
f(1000)[[1]]
## [1] 4252
使用
g = function(...) length(serialize(environment(), NULL))
f = function(n) {
x = sample(n)
parLapply(cl, 1, g)
}
f(1)[[1]]
## [1] 150
f(1000)[[1]]
## [1] 150
答案 1 :(得分:0)
在处理快要结束时,我正在将近50 GB的数据传递回parLapply
,这是不理想的。
我最终创建了一个名为parLapply
的新函数。我将其放在嵌套循环中,在其中创建了一个新环境,将父环境设置为.GlobalEnv
,仅将所需的变量传递给新环境,然后将该环境传递给clusterExport
。
有关环境的详细信息,我建议this博客文章。此外,我发现Ethan McCallum和Stephen Weston撰写的Parallel R书也很有帮助。在第15-17页上,“雪”包中有关于此问题的讨论。