使用mclapply并行处理大对象时性能劣于串行的原因及解决方法
使用mclapply并行处理大对象时性能劣于串行的原因及解决方法
嗨,我来帮你拆解下这个问题~你本来以为用mclapply并行处理50个耗时任务(每个几十毫秒),加上fork机制不会拷贝大对象,肯定比串行快,结果反而更慢,对吧?其实问题出在结果返回的开销上,咱们一步步说:
为什么并行反而更慢?
你一开始觉得任务量足够大,不会有并行开销,但忽略了一个关键点:fork机制虽然让子进程共享父进程的内存(读时复制,所以读取大对象M和B不会额外拷贝),但当子进程要把计算结果传回父进程时,这些结果需要被序列化、跨进程传输。
在你第一个测试示例里,每个任务返回的是x %*% B,这个计算结果的尺寸不小——500个这样的大对象从子进程传到父进程,这部分的开销直接盖过了并行计算节省的时间,导致整体性能反而不如串行的lapply。
怎么解决这个问题?
你后来修改的示例就找对了方向:把任务改成计算Matrix::t(B) %*% x %*% B,这时候返回的对象尺寸小了很多,结果传输的开销大幅降低,并行的优势就体现出来了。
咱们把两个测试代码放出来对比下:
性能劣于串行的示例
library(Matrix) library(microbenchmark) M = Matrix::bdiag(lapply(seq(5000), function(i)matrix(rnorm(9),3))) M_list = list();for(i in seq(500))M_list[[i]]=M B = Matrix::sparseMatrix(i = seq(15000), j = ceiling(50*runif(15000)), x = rnorm(15000)) microbenchmark::microbenchmark( lapply(M_list, FUN = function(x, B) { x %*% B }, B = B), parallel::mclapply(mc.cores = 4, M_list, FUN = function(x, B) { x %*% B}, B = B) , times = 5 )
并行恢复优势的示例
library(Matrix) library(microbenchmark) M = Matrix::bdiag(lapply(seq(5000), function(i)matrix(rnorm(9),3))) M_list = list();for(i in seq(500))M_list[[i]]=M B = Matrix::sparseMatrix(i = seq(15000), j = ceiling(50*runif(15000)), x = rnorm(15000)) microbenchmark::microbenchmark( lapply(M_list, FUN = function(x, B) { x %*% B }, B = B), parallel::mclapply(mc.cores = 4, M_list, FUN = function(x, B) { Matrix::t(B) %*% x %*% B}, B = B) , times = 5 )
额外的小建议
- 评估并行收益时,一定要把结果序列化/传输的成本算进去,尤其是返回大对象的场景,这部分开销很容易被忽略。
- 如果你的业务场景必须返回大对象,可以考虑让子进程直接把结果写入文件(比如用
saveRDS),而不是传回父进程,这样能避免跨进程传输大对象的开销。 - 对于fork模式的并行(比如
mclapply),子进程读取父进程的大对象确实无拷贝,但只要涉及到返回新的大对象,传输开销就会出现,这点要牢记。
备注:内容来源于stack exchange,提问作者SCS
相关产品推荐
相关产品推荐

