并行*在功能内

时间:2014-12-15 20:50:26

标签: r parallel-processing 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)

然而,这会引发错误

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

所以我做了一些研究,发现如果我手动导出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)

它给出了正确的结果

  type x.x x.y
1    a   1   3
2    b   2   4

但我也发现以下代码有效

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)

plyr_test_3()也会给出正确的结果,但我不明白为什么。我原以为我必须导出df_2 ...

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

3 个答案:

答案 0 :(得分:1)

看起来像是一个范围问题。

这是我的测试套件"这允许我.export不同的变量或避免在函数内创建df_2。我在函数外部添加和删除虚拟df_2和df_3并进行比较。

library(plyr)
library(doParallel)

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

plyr_test=function(exportvar,makedf_2) {
  df_1=data.frame(type=c("a","b"),x=1:2)
  if(makedf_2){
    df_2=data.frame(type=c("a","b"),x=3:4)
  }
  print(ls())

  ddply(df_1,"type",.parallel=TRUE,.paropts=list(.export=exportvar,.verbose = TRUE),.fun=function(y) {
    z <- merge(y,df_2,all=FALSE,by="type")
  })
}
ls()
rm(df_2,df_3)
plyr_test("df_2",T)
plyr_test("df_2",F)
plyr_test("df_3",T)
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
df_2='hi'
ls()
plyr_test("df_2",T) #ok
plyr_test("df_2",F)
plyr_test("df_3",T)
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
df_3 = 'hi'
ls()
plyr_test("df_2",T) #ok
plyr_test("df_2",F)
plyr_test("df_3",T) #ok
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)
rm(df_2)
ls()
plyr_test("df_2",T)
plyr_test("df_2",F)
plyr_test("df_3",T) #ok
plyr_test("df_3",F)
plyr_test(NULL,T) #ok
plyr_test(NULL,F)

我不知道为什么,但.export在函数外部的全局环境中查找df_2,(我在代码中看到了parent.env(),这可能是&#34;更正确&#34 ;而不是全局环境),而计算要求变量与ddply在同一环境中并自动导出它。

在函数外部使用df_2的虚拟变量允许.export工作,而计算使用内部的df_2。

当.export无法在函数外找到变量时,它会输出:

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

在函数外部使用df_2虚拟变量,但没有内部函数,.export很好,但ddply输出:

Error in do.ply(i) : task 1 failed - "object 'df_2' not found"

可能因为这是一个小例子或者可能不可并行化,它实际上在一个核心上运行并且无需导出任何东西。如果没有.export,一个更大的例子可能会失败,但其他人可以试试。

答案 1 :(得分:1)

plyr_test的问题是df_2中定义的plyr_test无法从doParallel包中访问,因此在尝试导出时失败{ {1}}。这是一个范围问题。 df_2可以避免此问题,因为它不会尝试使用plyr_test2选项,但正如您所猜测的那样,不需要调用.export

clusterExportplyr_test2成功的原因是plyr_test3与通过{{1}传递给df_2函数的匿名函数一起序列化参数。实际上,ddply.fun都与匿名函数一起序列化,因为该函数是在df_1df_2中定义的。在这种情况下包含plyr_test2会很有帮助,但包含plyr_test3是不必要的,可能会影响您的表现。

只要在匿名函数的环境中捕获df_2,就不会使用df_1的其他值,无论您导出什么。除非您可以阻止它被捕获,否则使用df_2df_2导出它是没有意义的,因为将使用捕获的值。您只能通过尝试将其导出给工作人员来解决问题(正如您对.export所做的那样)。

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

您可以通过在将df_2传递给.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) } 包的一个优点是它不鼓励你在另一个可能意外捕获一堆变量的函数内部创建一个函数。


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

答案 2 :(得分:0)

感谢@ARobertson的帮助! 当在函数体之外定义虚拟对象plyr_test("df_2",T)df_2工作非常有趣。

看起来ddply最终会调用llply,而foreach(...) %dopar% {...}会调用foreach

我还尝试使用foreach重现问题,但library(plyr) library(doParallel) workers=makeCluster(2) registerDoParallel(workers,core=2) foreach_test=function() { df_1=data.frame(type=c("a","b"),x=1:2) df_2=data.frame(type=c("a","b"),x=3:4) foreach(y=split(df_1,df_1$type),.combine="rbind",.export="df_2") %dopar% { #also print process ID to be sure that we really use different R script processes cbind(merge(y,df_2,all=FALSE,by="type"),Sys.getpid()) } } foreach_test() stopCluster(workers) 工作正常。

Warning message:
In e$fun(obj, substitute(ex), parent.frame(), e$data) :
  already exporting variable(s): df_2

它会抛出警告

  type x.x x.y Sys.getpid()
1    a   1   3          216
2    b   2   4         1336

但它返回正确的结果

foreach

因此,df_2似乎会自动导出foreach。实际上,.export="df_2" vignette表示

  

...%dopar%函数注意到那些变量被引用,并且   它们是在当前环境中定义的。在这种情况下   %dopar%会自动将它们导出到并行执行   工人一次,并将它们用于所有的表达式评估   那个预先执行......

因此我们可以省略library(plyr) library(doParallel) workers=makeCluster(2) registerDoParallel(workers,core=2) foreach_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) foreach(y=split(df_1,df_1$type),.combine="rbind") %dopar% { #also print process ID to be sure that we really use different R script processes cbind(merge(y,df_2,all=FALSE,by="type"),Sys.getpid()) } } foreach_test_2() stopCluster(workers) 并使用

foreach

代替。这会在没有警告的情况下进行评估。

ARobertson的虚拟变量示例以及plyr_test_3()正常工作的事实让我现在认为* ply如何处理环境存在问题。

我的结论是:

函数foreach_test_2()df_2(未明确导出ddply)运行时没有错误并给出相同的结果。因此,parallel=TRUEdf_2基本相同。但是使用更加“冗长”的编码样式(即明确导出plyr_test()),例如在foreach(...) %dopar% {...}中会引发错误,而{{1}}只会发出警告。