6

我在 Windows 7 和 Linux(SUSE Server 11 (x86_64))上都使用 R 3.0.1。以下示例代码在 Windows 上会产生错误,但在 Linux 上不会。列出的所有工具箱在两台机器上都是最新的。Windows 错误是:

Error in { : task 1 failed - "NULL value passed as symbol address"

如果我更改%dopar% to %do%,Windows 代码运行没有任何错误。我最初的猜测是这与 Windows 中的一些配置问题有关,我尝试重新安装 Rcpp 和 R,但这没有帮助。该错误似乎与范围有关 - 如果我在 f1 中定义和编译函数 cFunc,那么%dopar%它可以工作,但正如预期的那样,它非常慢,因为我们为每个任务调用一次编译器。

有没有人对错误发生的原因有一些见解或有关如何修复它的建议?

library(inline)
sigFunc <- signature(x="numeric", size_x="numeric")
code <- ' double tot =0;
for(int k = 0; k < INTEGER(size_x)[0]; k++){
tot += REAL(x)[k];
};
return ScalarReal(tot);
' 
cFunc <- cxxfunction(sigFunc, code)

f1 <- function(){
x <- rnorm(100)
a <- cFunc(x=x, size_x=as.integer(length(x)))
return(a)
}

library(foreach)
library(doParallel)
registerDoParallel()
# this produces an error in Windows but not in Linux
res <- foreach(counter=(1:100)) %dopar% {f1()}
# this works for both Windows and Linux
res <- foreach(counter=(1:100)) %do% {f1()}

# The following is not a practical solution, but I can compile cFunc inside f1 and then     this works in Windows but it is very slow
f1 <- function(){
library(inline)
sigFunc <- signature(x="numeric", size_x="numeric")

code <- ' double tot =0;
for(int k = 0; k < INTEGER(size_x)[0]; k++){
tot += REAL(x)[k];
};
return ScalarReal(tot);
' 
cFunc <- cxxfunction(sigFunc, code)
x <- rnorm(100)
a <- cFunc(x=x, size_x=as.integer(length(x)))
return(a)
}
# this now works in Windows but is very slow
res <- foreach(counter=(1:100)) %dopar% {f1()}

谢谢!古斯塔沃

4

2 回答 2

6

错误消息“作为符号地址传递的 NULL 值”是不寻常的,不是由于函数未导出到工作人员。该cFunc功能在被序列化、发送给工作人员和反序列化后不起作用。从保存的工作区加载时它也不起作用,这会导致相同的错误消息。这并不让我感到惊讶,它可能是inline包的记录行为。

正如您所展示的,您可以通过cFunc在工作人员上创建来解决问题。为了有效地做到这一点,您只需对每个工人执行一次。为了在后端做到这一点doParallel,我将定义一个工作器初始化函数,并使用该函数在每个工作器上执行它clusterCall

worker.init <- function() {
  library(inline)
  sigFunc <- signature(x="numeric", size_x="numeric")
  code <- ' double tot =0;
  for(int k = 0; k < INTEGER(size_x)[0]; k++){
  tot += REAL(x)[k];
  };
  return ScalarReal(tot);
  '
  assign('cFunc', cxxfunction(sigFunc, code), .GlobalEnv)
  NULL
}

f1 <- function(){
  x <- rnorm(100)
  a <- cFunc(x=x, size_x=as.integer(length(x)))
  return(a)
}

library(foreach)
library(doParallel)
cl <- makePSOCKcluster(3)
clusterCall(cl, worker.init)
registerDoParallel(cl)
res <- foreach(counter=1:100) %dopar% f1()

请注意,您必须显式创建 PSOCK 集群对象才能调用clusterCall.

您的示例在 Linux 上运行的原因是,mclapply当您在registerDoParallel没有参数的情况下调用时会使用该函数,而在 Windows 上会创建一个集群对象并clusterApplyLB使用该函数。函数和变量在使用的时候没有序列化发送给worker mclapply,所以不会出错。

如果doParallel包含对初始化工作人员的支持而无需使用clusterCall,那就太好了,但目前还没有。

于 2013-08-15T02:58:27.200 回答
4

我认为最简单的“解决方法”是

1)将你的代码写在一个单独的源文件中,比如cFunc.c

2)编译它R CMD SHLIB

3)dyn.load你的电话中的那个功能foreach

例如,

cFunc.c
=======

#include <R.h>
#include <Rinternals.h>

SEXP cFunc( SEXP x, SEXP size_x ) {

  double tot = 0;
  for (int k=0; k < INTEGER(size_x)[0]; ++k ) {
    tot += REAL(x)[k];
  }
  return ScalarReal(tot);

}

library(foreach)
library(doParallel)
registerDoParallel()
x <- as.numeric(1:100)
size_x <- as.integer(length(x))
res <- foreach(counter=(1:100)) %dopar% { 
  dyn.load("cFunc.dll")
  .Call("cFunc", x, size_x) 
}

或者(可能更好),考虑使用导出的这个函数构建一个实际的包,您可以使用它。

于 2013-08-15T02:54:32.547 回答