R语言for循环内并行计算速度慢于串行的原因及解决方案
问题根本原因
并行版本运行速度慢于串行的核心原因是并行固定开销远大于实际计算收益,且最重的开销步骤被放在循环内反复执行,结合Windows系统特性具体拆解:
- Windows系统下
parallel包默认创建的是PSOCK集群,每次执行makeCluster()会启动多个独立的R子进程,完成进程初始化、环境加载、通信链路建立的操作;执行stopCluster()会销毁所有子进程、释放资源。这两个步骤的固定开销在毫秒到百毫秒级,远高于示例中单次并行任务的计算量——示例里分配给每个子进程的任务只是生成4个正态随机数,计算本身耗时仅几微秒。 - 原代码把集群创建、销毁、核心数检测的逻辑放在了1000次的
for循环内部,相当于每次迭代都要重复支付一次集群启停的高额成本,累计开销远大于串行计算本身的耗时,最终速度远低于串行版本。 - 示例中的串行逻辑本身计算量极小,1000次循环累计生成40万个随机数,总耗时通常不到0.1秒,这种量级的计算本身就不适合用并行,并行的收益只有在单任务计算量足够大、能覆盖通信和进程管理开销的时候才能体现。
可行解决方法
- 将集群初始化、销毁逻辑移到循环外部,仅执行一次。这是改动最小、效果最明显的方案,避免反复启停集群的冗余开销,修正后的示例代码如下:
library(parallel) N_iter = 1000 N = 100 # 串行版本计时 begin_normalprocedure = Sys.time() for (i in 1:N_iter){ for (j in 1:N){ rnorm(4) } } end_normalprocedure = Sys.time() print(paste("串行版本耗时: ", round(end_normalprocedure - begin_normalprocedure, 3), "秒")) # 修正后的并行版本 begin_parallelprocedure = Sys.time() ncores <- detectCores(logical=F) # 核心数仅检测一次 clust <- makeCluster(ncores) # 集群仅在所有循环开始前启动一次 xlist <- sapply(1:N, list) for (i in 1:N_iter){ parLapply(clust, xlist, function(x) {rnorm(4)}) # 循环内仅执行实际计算逻辑 } stopCluster(clust) # 所有循环计算完成后再关闭集群 end_parallelprocedure = Sys.time() print(paste("修正后并行版本耗时: ", round(end_parallelprocedure - begin_parallelprocedure, 3), "秒"))
- 调大并行任务粒度,减少通信次数。如果实际业务里单个模拟任务的计算量和示例一样小,不要把极小的任务拆分给各个核心,可以把多个小模拟任务打包成一个大任务分配给子进程,降低进程间数据传输、任务调度的频次,让并行的收益覆盖开销。
- 对于计算量极小的场景,不要强行使用并行。本身毫秒级以内就能跑完的计算,用并行无论怎么优化都会因为进程管理、通信的额外开销变得更慢,直接用串行向量化操作(比如直接一次性生成所有需要的随机数)效率最高。
内容的提问来源于stack exchange,提问作者ValentinaGhidini
相关产品推荐
相关产品推荐

