以下代码在Windows和Ubuntu平台上产生不同的结果。据我所知,这是因为处理并行处理的方法不同。
汇总:
我不能并行地(insert
,rbind
)在Linux上 mclapply
/ mcmapply
数据,而我可以在Windows上执行。
感谢@Hong Ooi指出
mclapply
并不适用于Windows,但问题仍然存在。
当然,同一data.frame
没有多个插入,每个插入都执行到单独的data.frame。
library(R6)
library(parallel)
# storage objects generator
cl <- R6Class(
classname = "cl",
public = list(
data = data.frame(NULL),
initialize = function() invisible(self),
insert = function(x) self$data <- rbind(self$data, x)
)
)
N <- 4L # number of entities
i <- setNames(seq_len(N),paste0("n",seq_len(N)))
# random data.frames
set.seed(1)
ldt <- lapply(i, function(i) data.frame(replicate(sample(3:10,1),sample(letters,1e5,rep=TRUE))))
# entity storage
lcl1 <- lapply(i, function(i) cl$new())
lcl2 <- lapply(i, function(i) cl$new())
lcl3 <- lapply(i, function(i) cl$new())
# insert data
invisible({
mclapply(names(i), FUN = function(n) lcl1[[n]]$insert(ldt[[n]]))
mcmapply(FUN = function(dt, cl) cl$insert(dt), ldt, lcl2, SIMPLIFY=FALSE)
lapply(names(i), FUN = function(n) lcl3[[n]]$insert(ldt[[n]]))
})
### Windows
sapply(lcl1, function(cl) nrow(cl$data)) # mclapply
# n1 n2 n3 n4
# 100000 100000 100000 100000
sapply(lcl2, function(cl) nrow(cl$data)) # mcmapply
# n1 n2 n3 n4
# 100000 100000 100000 100000
sapply(lcl3, function(cl) nrow(cl$data)) # lapply
# n1 n2 n3 n4
# 100000 100000 100000 100000
### Unix
sapply(lcl1, function(cl) nrow(cl$data)) # mclapply
#n1 n2 n3 n4
# 0 0 0 0
sapply(lcl2, function(cl) nrow(cl$data)) # mcmapply
#n1 n2 n3 n4
# 0 0 0 0
sapply(lcl3, function(cl) nrow(cl$data)) # lapply
# n1 n2 n3 n4
# 100000 100000 100000 100000
问题是:
rbind
平行地分配到单独的data.frame
中? P.S。在我的情况下,像SQLite
这样的内存存储不能被视为解决方案。
答案 0 :(得分:3)
问题是mclapply
和mcmapply
不适用于具有副作用的函数。您的函数正在修改列表中的对象,但mclapply
不会将修改后的对象发送回主进程:它只返回函数显式返回的值。这意味着当工作人员退出mclapply
时,您的结果将会丢失。
通常我会将代码更改为不依赖于副作用,并返回已修改的对象。这是使用clusterApply
执行此操作的一种方法,以便它也可以在Windows上并行运行:
library(R6)
library(parallel)
cl <- R6Class(
classname = "cl",
public = list(
data = data.frame(NULL),
initialize = function() invisible(self),
insert = function(x) self$data <- rbind(self$data, x)))
N <- 4L # number of entities
i <- setNames(seq_len(N),paste0("n",seq_len(N)))
set.seed(1)
ldt <- lapply(i, function(i)
data.frame(replicate(sample(3:10,1),sample(letters,1e5,rep=TRUE))))
nw <- 3 # number of workers
clust <- makePSOCKcluster(nw)
idx <- splitIndices(length(i), nw)
nameslist <- lapply(idx, function(iv) names(i)[iv])
lcl4 <- do.call('c', clusterApply(clust, nameslist,
function(nms, cl, ldt) {
library(R6)
lcl4 <- lapply(nms, function(n) cl$new())
names(lcl4) <- nms
lapply(nms, FUN = function(n) lcl4[[n]]$insert(ldt[[n]]))
lcl4
}, cl, ldt))
如果要创建一次对象列表,然后多次并行修改对象,则此方法不起作用。这也是可能的,但你必须有持久的工人。在这种情况下,您将在完成所有任务后从工作程序中获取已修改的对象。遗憾的是,mclapply
不使用持久性工作者,因此在这种情况下,您必须使用基于群集的函数,例如clusterApply
。这是一种方法:
# Initialize the cluster workers
clusterEvalQ(clust, library(R6))
clusterExport(clust, c('cl', 'ldt'))
clusterApply(clust, nameslist, function(nms) {
x <- lapply(nms, function(n) cl$new())
names(x) <- nms
assign('lcl4', x, pos=.GlobalEnv)
NULL
})
# Insert data into lcl4 on each worker
clusterApply(clust, nameslist, function(nms) {
lapply(nms, FUN = function(n) lcl4[[n]]$insert(ldt[[n]]))
NULL
})
# Concatenate lcl4 from each worker
lcl4 <- do.call('c', clusterEvalQ(clust, lcl4))
这与前一种方法非常相似,只是它将流程分为三个阶段:工作者初始化,任务执行和结果检索。我还使用clusterExport
和clusterEvalQ
以更传统的方式初始化了工作人员。
答案 1 :(得分:0)
我认为mclapply
的Windows版本正在运行,因为它将其作业委托给lapply
。检查时序或CPU核心使用情况可以验证这一点。根据{{3}},Windows mclapply
和mcmapply
将替换为顺序版本。
看来,代码并行化的方式出了问题,目前无法确切地看到代码。