为何R中foreach并行矩阵计算未提速?如何优化?
问题描述
尝试用foreach并行化矩阵计算,但即便使用10核,其性能与apply、for循环相近。测试代码如下:
library(microbenchmark) library(foreach) library(doParallel) mat = matrix(rnorm(3000 * 3000), 3000) # 3000 X 3000 g = rnorm(3000) # 3000 # 使用10核 cl <- makeCluster(10) registerDoParallel(cl) microbenchmark( # 多核foreach foreach(i = 1:100, .combine = c) %dopar% {t(g) %*% mat %*% g}, # sapply串行 sapply(1:100, function(i){t(g) %*% mat %*% g}), # for循环串行 for(i in 1:100){t(g) %*% mat %*% g}, times = 20) stopCluster(cl)
测试结果显示三者性能相近。而使用Sys.sleep()测试时,foreach性能约为sapply和for循环的10倍,测试代码如下:
cl <- makeCluster(10) registerDoParallel(cl) microbenchmark( # 多核foreach foreach(i = 1:100, .combine = c) %dopar% {Sys.sleep(0.01) }, # sapply串行 sapply(1:100, function(i){Sys.sleep(0.01)}), # for循环串行 for(i in 1:100){Sys.sleep(0.01)}, times = 20) stopCluster(cl)
请问出现这种差异的原因是什么?如何优化矩阵计算的并行性能?
原因分析
- 并行开销抵消计算收益:
t(g) %*% mat %*% g是计算二次型,底层由BLAS库优化,单次计算速度极快。但并行计算需要完成数据传递(把mat和g传给子进程)、进程通信、结果合并这些额外操作,这些开销远大于并行计算节省的时间,导致整体性能和串行方式持平。 - Sys.sleep场景无额外开销:
Sys.sleep(0.01)是纯阻塞等待,几乎没有计算量,也不需要传递大对象。此时10核可以同时处理10个sleep任务,并行调度的优势完全发挥,总耗时接近串行的1/10。
优化矩阵计算并行性能的方法
- 合并小任务,减少调度开销:不要把极小的计算拆分到多个核,而是把任务打包成更大的块。比如把100次计算分成10组,每组10次,让每个核处理一组,大幅减少进程通信次数。示例代码:
foreach(i = 1:10, .combine = c) %dopar% { replicate(10, t(g) %*% mat %*% g) } - 避免无意义的重复计算:你的测试中
t(g) %*% mat %*% g的结果是固定值,重复计算完全没必要。如果实际场景是类似但参数不同的计算(比如每次用不同的g),确保每个核分配到的计算量足够大,让计算时间远超过并行开销。 - 提前传递大对象,减少数据复制:用
clusterExport()提前把大对象(比如mat)传给所有子进程,避免每次迭代都重复传递。示例代码:cl <- makeCluster(10) clusterExport(cl, c("mat", "g")) # 提前导出大对象到子进程 registerDoParallel(cl) # 后续并行计算代码 stopCluster(cl) - 利用BLAS底层多线程:矩阵运算依赖的BLAS库(如OpenBLAS、MKL)本身支持多线程,直接设置环境变量即可开启,这种方式的开销远低于进程级并行,适合单一大计算任务的并行化。示例:
# 针对OpenBLAS设置10线程 Sys.setenv(OPENBLAS_NUM_THREADS=10) # 针对MKL设置10线程 Sys.setenv(MKL_NUM_THREADS=10)
内容的提问来源于stack exchange,提问作者Taotao Tan
相关产品推荐
相关产品推荐

