【问题标题】:Strange environment behavior in parallel plyr并行 plyr 中的奇怪环境行为
【发布时间】:2013-07-24 16:48:41
【问题描述】:

最近,我在自己的工作区创建了一个对象factor=1,不知道base包中有一个函数factor

我打算做的是在并行循环中使用变量factor,例如,

library(plyr)
library(foreach)
library(doParallel)

workers <- makeCluster(2)
registerDoParallel(workers,cores=2)

factor=1

llply(
  as.list(1:2),
  function(x) factor*x,
  .parallel = TRUE,
  .paropts=list(.export=c("factor"))
     )

然而,这导致了一个错误,我花了很长时间才理解。看起来,plyr 在其环境exportEnv 中创建了对象factor,但使用base::factor 而不是用户提供的对象。看下面的例子

llply(
  as.list(1:2),
  function(x) {
    function_env=environment();
    global_env=parent.env(function_env);
    export_env=parent.env(global_env);
    list(
      function_env=function_env,
      global_env=global_env,
      export_env=export_env,
      objects_in_exportenv=unlist(ls(envir=export_env)),
      factor_found_in_envs=find("factor"),
      factor_in_exportenv=get("factor",envir=export_env)
      )
    },
  .parallel = TRUE,
  .paropts=list(.export=c("factor"))
  )

stopCluster(workers)

如果我们检查llply 的输出,我们会看到factor_in_exportenv=get("factor",envir=export_env) 行没有返回1(对应于用户提供的对象)而是base::factor 的函数定义。

问题 1) 我如何理解这种行为?我本来希望输出是1

问题 2) 如果我为已在另一个包中定义的对象(例如 factor)分配新值,是否有办法从 R 获得警告?

【问题讨论】:

    标签: r parallel-processing plyr


    【解决方案1】:

    llply 函数在后台调用“foreach”。 Foreach 使用“parant.frame()”来确定要评估的环境。 llply 案例中的 parant.frame 是什么?它是 llply 的函数环境,没有定义因子。

    不使用llply,为什么不直接使用foreach呢?

    library(plyr)
    library(foreach)
    library(doParallel)
    
    workers <- makeCluster(2)
    registerDoParallel(workers,cores=2)
    
    factor=1
    foreach(x=1:2) %dopar% {factor*x}
    

    请注意,您甚至不需要 .export 参数,因为在这种情况下它会自动这样做。

    【讨论】:

    • 感谢您的意见。这里的问题与其说是评估环境,不如说是foreach 使用什么环境来查找应该导出的变量。如果使用base 中未使用的变量名,问题就消失了,比如说a 而不是factor。我知道我可以直接使用foreach (就像你和我现在大部分时间一样),然后错误就消失了。但早在 2013 年,我是一个重度 plyr 用户,这个错误让我非常困惑。所以,出于好奇,我想解决这个问题。
    【解决方案2】:

    首先,我应该注意,如果使用base 中未使用的另一个变量名,错误就会消失——例如,如果我们使用a 而不是factor。这清楚地表明llply 在其搜索路径中找到factor(值为1 的变量)之前的base::factor(一个函数)。我试图用llply 的简化版本来复制这个问题,即,

    library(plyr)
    library(foreach)
    library(doParallel)
    
    workers <- makeCluster(2)
    registerDoParallel(workers,cores=2)
    
    factor=1
    
    llply_simple=function(.x,.fun,.paropts) {
      #give current environment a name
      tmpEnv=environment()
      attr(tmpEnv,"name")="llply_simple_body"
      #print all enclosing envirs of llply_simple_body (see def of allEnv below)
      print(allEnv(tmpEnv))
      cat("------\nResults:\n")
      do.ply=function(i) {
        .fun(i)
      }
      fe_call <- as.call(c(list(quote(foreach::foreach), i = .x), .paropts))
      fe <- eval(fe_call)
      foreach::`%dopar%`(fe, do.ply(i))
    }
    

    llply_simple 使用递归辅助函数 (allEnv) 循环遍历所有封闭环境。它返回一个包含所有环境名称的向量

    allEnv=function(x) {
      if (environmentName(x)=="R_EmptyEnv") {
        return(environmentName(x))
      } else {
        c(environmentName(x),allEnv(parent.env(x)))
      }
    }
    

    有趣的是,简化函数实际上按预期工作(即,给出12 作为结果)

    llply_simple(1:2,function(x) x*factor,list(.export="factor"))
    #[1] "llply_simple_body"  "R_GlobalEnv"        "package:doParallel" "package:parallel"  
    #[5] "package:iterators"  "package:foreach"    "package:plyr"       "tools:rstudio"     
    #[9] "package:stats"      "package:graphics"   "package:grDevices"  "package:utils"     
    #[13] "package:datasets"   "package:methods"    "Autoloads"          "base"              
    #[17] "R_EmptyEnv"
    #--------
    #Results:        
    #[[1]]
    #[1] 1
    #
    #[[2]]
    #[1] 2
    

    所以llply_simple 与完整的plyr::llply 函数的唯一显着区别是后者属于一个包。让我们尝试将llply_simple 移动到一个包中。

    package.skeleton(list=c("llply_simple","allEnv"),name="llplyTest")
    unlink("./llplyTest/DESCRIPTION")
    devtools::create_description("./llplyTest",
                                 extra=list("devtools.desc.author"='"T <t@t.com>"'))
    tmp=readLines("./llplyTest/man/llply_simple.Rd")
    tmp[which(grepl("\\\\title",tmp))+1]="Test1"
    writeLines(tmp,"./llplyTest/man/llply_simple.Rd")
    tmp=readLines("./llplyTest/man/allEnv.Rd")
    tmp[which(grepl("\\\\title",tmp))+1]="Test2"
    writeLines(tmp,"./llplyTest/man/allEnv.Rd")
    devtools::install("./llplyTest")
    

    现在尝试从我们的新包llplyTest执行llplyTest::llply_simple

    library(llplyTest)
    llplyTest::llply_simple(1:2,function(x) x*factor,list(.export="factor"))
    #[1] "llply_simple_body"  "llplyTest"          "imports:llplyTest"  "base"              
    #[5] "R_GlobalEnv"        "package:doParallel" "package:parallel"   "package:iterators" 
    #[9] "package:foreach"    "package:plyr"       "tools:rstudio"      "package:stats"     
    #[13] "package:graphics"   "package:grDevices"  "package:utils"      "package:datasets"  
    #[17] "package:methods"    "Autoloads"          "base"               "R_EmptyEnv"
    #------
    #Results:
    #Error in do.ply(i) : 
    #  task 1 failed - "non-numeric argument to binary operator"
    

    突然之间,我们遇到了与我在 2013 年的原始问题相同的错误。因此,问题显然与从包中调用函数有关。让我们看看allEnv 的输出:它基本上为我们提供了llpy_simplellplyTest::llpy_simple 用于查找应该导出的变量的环境序列。实际上是foreach 进行了导出,如果有人有兴趣了解为什么foreach 真的从我们命名为llply_simple_body 的环境开始,请查看foreach::%dopar%foreach:::getDoParforeach:::.foreachGlobals$fun 的源代码和遵循envir 参数的路径。

    我们现在可以清楚地看到,非包版本的搜索顺序与llplyTest::llpy_simple 不同,并且包版本将首先在base 中找到factor

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      • 2013-11-15
      • 1970-01-01
      相关资源
      最近更新 更多