使用%dopar%执行data.table并行操作过慢的问题排查
为什么我的foreach并行循环比lapply慢这么多?
你遇到的情况很典型——并行代码看起来在忙碌运行,但速度反而不如串行的lapply,问题出在你的并行循环写法上,我来帮你拆解:
原并行代码的核心问题
先看你原来的并行实现:
cl <- makeCluster(4) registerDoParallel(cl) grouped_data_list_2 <- list() foreach( i = 1:length(grouped_data_list) ) %dopar% { x <- grouped_data_list[[i]] data.table::setkey(x, Date) dt_params <- unlist( x[1, -c('Date', 'Count'), with = F] ) y <- x[reported_date_seq_dt] y[is.na(Count), (colnames(y)[!colnames(y) %in% c('Date', 'Count')]) := lapply(1:length(dt_params), function(x) dt_params[x])] y[is.na(Count), Count := 0] grouped_data_list_2 <- c(grouped_data_list_2 , list(y) ) } stopCluster(cl)
这里有两个致命的低效点:
- 无效的全局列表修改:你在循环里尝试拼接
grouped_data_list_2,但并行环境中每个工作进程都有独立的内存空间,这个修改只会在当前进程里生效,主进程的grouped_data_list_2最终还是空的。而且反复拼接列表本身会产生大量内存复制,再加上进程间的隐性通信,直接拖慢了速度。 - 索引遍历的额外开销:用
i = 1:length(grouped_data_list)再取grouped_data_list[[i]],这种方式不仅代码冗余,还会增加索引查找的额外开销,不如直接遍历列表元素高效。
修正后的高效并行代码
按照@Roland的建议修改后,代码就会和lapply一样快,甚至在数据量更大时体现出并行优势:
cl <- makeCluster(4) registerDoParallel(cl) # 直接遍历列表元素,foreach自动收集结果 grouped_data_list_2 <- foreach( x = grouped_data_list ) %dopar% { data.table::setkey(x, Date) dt_params <- unlist( x[1, -c('Date', 'Count'), with = F] ) y <- x[reported_date_seq_dt] y[is.na(Count), (colnames(y)[!colnames(y) %in% c('Date', 'Count')]) := lapply(1:length(dt_params), function(x) dt_params[x])] y[is.na(Count), Count := 0] y # 每个迭代返回处理后的结果,foreach自动组合成列表 } stopCluster(cl)
为什么这个版本更快?
- 直接遍历元素:省去了索引查找的步骤,代码更简洁,也减少了不必要的操作。
- 自动收集结果:
foreach会自动把每个迭代返回的y组合成最终的grouped_data_list_2,完全避免了并行环境中无效的全局变量修改,消除了额外的内存开销和通信成本。
这样修改后,你的并行代码就会和串行的lapply逻辑一致,同时利用多核心的优势,不会再出现速度慢的问题。
内容的提问来源于stack exchange,提问作者alexeymosco
相关产品推荐
相关产品推荐

