这是一个很好的例子,说明将问题分解为碎片的步骤如何真正影响并行计算的好处。我遗漏了您的一些代码(例如协变量的对数转换和缺失值的消除),这对问题不是必不可少的。我认为您希望避免在每次调用时转置整个矩阵——只需在脚本顶部执行一次即可。AFAIK R 按列主要顺序存储数据,因此通过处理列来避免该步骤可能已经为您节省了一些时间。
在第一次试用中,我首先连续运行了一个版本,看看有多少改进。这是在 AMD Phenom 9850 Quad core 上,频率为 2.5 GHz,内存为 8 GB(太旧了)。
library(doParallel)
library(iterators)
#make covariate data
N = 100
P = 100000 # number of predictors
tt = as.data.frame(matrix(rnorm(N*P),nrow=N,ncol=P))
my.y = rbinom(N,p=0.5,size=1)
y = factor(my.y)
# How fast to do it serially?
system.time(x1 <- foreach(cc = iter(tt, by='col'),.combine=c) %do% {
deviance(glm(y~cc, family = "binomial"))
}) # elapsed 718 s
nm.cores = detectCores() - 1
cl=makeCluster(nm.cores)
registerDoParallel(cl)
# send entire dataframe to each worker, pull out the desired column
system.time(x2 <- foreach(cnt=1:ncol(tt),.combine=c) %dopar% {
whol.dat = data.frame(tt[,cnt], y=factor(my.y))
deviance(glm(y~., data = whol.dat, family = "binomial"))
}) # elapsed 276 s, so 3 x faster
all.equal(x1,x2) # TRUE, just checkin' ...
我的第一个想法是每次将整个矩阵发送给每个工作人员可能会带来一些开销,所以我重写了foreach()
用于iter()
将每一列发送给工作人员:
system.time(x3 <- foreach(cc = iter(tt, by='col'),.combine=c) %dopar% {
deviance(glm(y~cc, family = "binomial"))
}) # not much faster, 248s
这确实加快了一些速度,但并不多。我以前没有使用过迭代器,所以在阅读 foreach 小插图时,我遇到了一个自定义迭代器iblkcol()
,它将 data.frame 分成块,并发送每个块以节省向工作人员发送数据和从工作人员那里取回数据的开销。代码隐藏在 Github 上(见第 199-218 行)。
## from vignette on foreach:
## use iblkcol() instead of iter in loop to send blocks of columns instead of one at a time
system.time(x4 <- foreach(cc = iblkcol(tt, chunks = nm.cores),.combine=c,.packages='foreach') %dopar% {
foreach(x = 1:col(cc),.combine=c) %do% {
deviance(glm(y~cc[,x], family = "binomial"))
}
}) # 193 s!
与一次发送每一列相比,这是一个重大改进。我认为可以通过调整对 glm() 的调用来利用大多数模型框架在一次调用到下一次调用中重复使用这一事实,可以实现一些额外的加速。同样的事情应该适用于对 wilcoxon() 的调用。