并行*在功能内

cry*_*111 5 parallel-processing r plyr

我想在函数中使用plyr包的并行功能.

我原本以为导出在函数体内创建的对象的正确方法(在本例中,对象是df_2)如下

# rm(list=ls())
library(plyr)
library(doParallel)

workers=makeCluster(2)
registerDoParallel(workers,core=2)

plyr_test=function() {
  df_1=data.frame(type=c("a","b"),x=1:2)
  df_2=data.frame(type=c("a","b"),x=3:4)

  #export df_2 via .paropts  
  ddply(df_1,"type",.parallel=TRUE,.paropts=list(.export="df_2"),.fun=function(y) {
    merge(y,df_2,all=FALSE,by="type")
  })
}
plyr_test()
stopCluster(workers)
Run Code Online (Sandbox Code Playgroud)

但是,这会引发错误

Error in e$fun(obj, substitute(ex), parent.frame(), e$data) : 
  unable to find variable "df_2"
Run Code Online (Sandbox Code Playgroud)

所以我做了一些研究,发现如果我df_2手动导出它就可以了

workers=makeCluster(2)
registerDoParallel(workers,core=2)

plyr_test_2=function() {
  df_1=data.frame(type=c("a","b"),x=1:2)
  df_2=data.frame(type=c("a","b"),x=3:4)

  #manually export df_2
  clusterExport(cl=workers,varlist=list("df_2"),envir=environment())

  ddply(df_1,"type",.parallel=TRUE,.fun=function(y) {
    merge(y,df_2,all=FALSE,by="type")
  })
}
plyr_test_2()
stopCluster(workers)
Run Code Online (Sandbox Code Playgroud)

它给出了正确的结果

  type x.x x.y
1    a   1   3
2    b   2   4
Run Code Online (Sandbox Code Playgroud)

但我也发现以下代码有效

workers=makeCluster(2)
registerDoParallel(workers,core=2)

plyr_test_3=function() {
  df_1=data.frame(type=c("a","b"),x=1:2)
  df_2=data.frame(type=c("a","b"),x=3:4)

  #no export at all!
  ddply(df_1,"type",.parallel=TRUE,.fun=function(y) {
    merge(y,df_2,all=FALSE,by="type")
  })
}
plyr_test_3()
stopCluster(workers)
Run Code Online (Sandbox Code Playgroud)

plyr_test_3()也给出了正确的结果,我不明白为什么.我原本以为我要出口df_2......

我的问题是:*ply在函数内处理并行的正确方法是什么?显然,plyr_test()是不正确的.我不知何故感觉手动输出plyr_test_2()是无用的.但我也认为这plyr_test_3()是一种糟糕的编码风格.有人可以详细说明吗?多谢你们!

Ste*_*ton 1

问题plyr_test是无法从包中访问df_2中定义的,因此当尝试导出 时会失败。所以这是一个范围界定问题。避免了这个问题,因为它不会尝试使用该选项,但正如您所猜测的,不需要调用。plyr_testdoParalleldf_2plyr_test2.exportclusterExport

plyr_test2和 两者都plyr_test3成功的原因是它与通过参数df_2传递给函数的匿名函数一起序列化。事实上, 和都与匿名函数一起序列化,因为该函数是在和内部定义的。在本例中包含是很有帮助的,但包含是不必要的,并且可能会损害您的性能。ddply.fundf_1df_2plyr_test2plyr_test3df_2df_1

只要df_2是在匿名函数的环境中捕获的,则df_2无论导出什么内容,都不会使用其他值。除非您可以防止它被捕获,否则使用.export或导出它是没有意义的clusterExport,因为将使用捕获的值。.export通过尝试将其导出给工人,您只会给自己带来麻烦(就像您所做的那样)。

请注意,在这种情况下,foreach 不会自动导出df_2,因为它无法分析匿名函数的主体以查看引用了哪些符号。如果您直接调用 foreach 而不使用匿名函数,那么它将看到引用并自动导出它,从而无需使用 显式导出它.export

您可以plyr_test通过在将 传递给 之前修改其环境来防止 的环境与匿名函数一起序列化ddply

plyr_test=function() {
  df_1=data.frame(type=c("a","b"),x=1:2)
  df_2=data.frame(type=c("a","b"),x=3:4)
  clusterExport(cl=workers,varlist=list("df_2"),envir=environment())
  fun=function(y) merge(y, df_2, all=FALSE, by="type")
  environment(fun)=globalenv()
  ddply(df_1,"type",.parallel=TRUE,.fun=fun)
}
Run Code Online (Sandbox Code Playgroud)

该包的优点之一foreach是它不鼓励您在另一个可能意外捕获一堆变量的函数内部创建一个函数。


这个问题向我建议foreach应该包括一个名为.exportenv类似于该选项的clusterExport envir选项。这将非常有帮助plyr,因为它将允许df_2使用正确导出.exportdf_2但是,除非从函数中删除包含的环境,否则仍然不会使用导出的值.fun