为了并行高效地执行这些计算,您需要使用分块,因为单个均值计算不需要太多时间。使用时foreach
,我经常使用来自itertools
用于分块的包。在这种情况下,我使用isplitVector
函数以便为每个工作人员生成一个任务。结果是向量,因此只需将它们相加即可将它们组合起来,这就是为什么r
向量必须初始化为零向量。
vadd <- function(a, ...) {
for (v in list(...))
a <- a + v
a
}
res <- foreach(ids=isplitVector(unique(td$id), chunks=workers),
.combine='vadd',
.multicombine=TRUE,
.inorder=FALSE) %dopar% {
r <- rep(0, NROW(td))
for (i in ids)
r[td$id == i] <- mean(td$val[td$id != i])
r
}
这是将原始顺序版本放入foreach
循环,但仅对数据的子集进行操作。由于每个工作人员只需要组合一个结果,因此几乎不需要后处理,因此运行效率非常高。
为了了解其执行情况,我使用以下数据集针对顺序版本和 Rolands 数据表版本对其进行了基准测试:
set.seed(107)
n <- 1000000
m <- 10000
td <- data.frame(val=rnorm(n), id=sample(m, n, replace=TRUE))
我之所以将其包括在内,是因为性能非常依赖于数据。您甚至可以通过使用不同的随机种子获得不同的性能结果。
以下是我的 Linux 机器(配备 Xeon CPU X5650 和 12 GB RAM)的一些基准测试结果:
-
顺序 for 循环 http://pastebin.com/zE8HbiT4:359秒
-
顺序数据表版本 http://pastebin.com/FbX2zLST:208秒
-
foreach/doParallel/PSOCK 有 4 个工作线程 http://pastebin.com/zQtwb9HH:104秒
因此,对于至少一个数据集,并行执行此计算是值得的。这不是一个完美的加速,但也不算太糟糕。为了在您自己的计算机上或使用不同的数据集运行任何这些基准测试,您可以通过上面的链接从 Pastebin 下载它们。
Update
在完成这些基准测试之后,我有兴趣使用data.table
with foreach
以获得更快的版本。这是我想到的(根据马修·道尔的建议):
cmean <- function(v, mine) if (mine) mean(v) else 0
nuniq <- length(unique(td$id))
res <- foreach(grps=isplitIndices(nuniq, chunks=workers),
.combine='vadd',
.multicombine=TRUE,
.inorder=FALSE,
.packages='data.table') %dopar% {
td[, means := cmean(td$val[-.I], .GRP %in% grps), by=id]
td$means
}
td
现在是一个data.table
目的。我用了isplitIndices
来自itertools
包生成与每个任务块关联的组号向量。这cmean
函数是一个包装器mean
对于不应在该任务块中计算的组返回零。它使用与非数据表版本相同的组合函数,因为任务结果相同。
在四个工作线程和相同的数据集的情况下,该版本的运行时间为 56.4 秒,与顺序数据表版本相比,加速了 3.7 秒,比顺序 for 循环快了 6.4 倍,成为明显的赢家。基准测试可以从pastebin下载here http://pastebin.com/wax1LyEg.