dopar中代码的流程优化

Posted

技术标签:

【中文标题】dopar中代码的流程优化【英文标题】:Process optimisation of code within dopar 【发布时间】:2020-06-15 08:03:36 【问题描述】:

我正在尝试优化我的代码以多次运行 glms,并且我想通过foreach 或其他更有效的方式来利用并行化。

如您所见; for 循环运行 270000 glms 大约需要 800 秒;而foreachdopar 不直观地需要永远(它要么崩溃,要么我强迫它在几个小时后停止)。

感谢您的帮助。

吉尼斯

library(data.table)
library(parallel)
library(doParallel)
library(foreach)
scen_bin <- expand.grid(n = c(10, 20, 30), rate1 = c(0.1, 0.2, 0.3),
  rate2 = c(0.5, 0.6, 0.9))

rep <- 10000
scen_sims <- rbindlist(replicate(rep, scen_bin, simplify = FALSE),
  idcol = TRUE)
scen_sims[, `:=`(glm, list(c(1L, 2L)))]

for (i in 1:270000) 
  set(scen_sims, i, 8L, list(glm(formula = c(rbinom(scen_sims$drug[i], 1L, scen_sims$Treatment_Rates[i]),
    rbinom(scen_sims$control[i], 1L, scen_sims$Comparator_Rates[i])) ~ factor(c(rep("Trt",
    scen_sims$drug[i]), rep("Cont", scen_sims$control[i]))), family = "binomial")))


split_scen_sims <- split(scen_sims, seq(1, 270000, length.out = 1000))


jh <- foreach(x = 1:1000, .packages = c("data.table")) %dopar% 
  jh <- split_scen_sims[[x]]
  for (i in 1:270000) 
    set(jh, i, 8L, list(glm(formula = c(rbinom(jh$n[i], 1L, jh$rate1[i]), rbinom(jh$n[i],
      1L, jh$rate1[i])) ~ factor(c(rep("Trt", jh$n[i]), rep("Cont", jh$n[i]))),
      family = "binomial")))
  
  return(jh)

【问题讨论】:

minimal 示例真的需要 800 秒才能运行吗? @Cole 这是我能得到的最接近的;接受有关如何优化它的建议! :) 你能让它重现吗?用于glm 的列都不存在 从并行会话覆盖主会话中的值通常是一个坏主意,并且会导致意外行为。更好的解决方案是并行创建每个模型,将它们返回到列表中,然后将该列表添加为新列(如果需要)。 感谢您指出这一点,嗯.. 谢谢。我使用 set 是因为我看到它非常有效。我仍然对为什么与多核并行会导致性能下降感到困惑。我真的很想知道,并行化此代码的最快方法。如果运行 100 个 glms 需要 6 秒,我希望使用 100 个内核我应该能够在 10 秒内运行 10000 个? 【参考方案1】:

首先要注意的是,在循环中使用提取函数$ 会使它的性能很差。最好 1) 创建一个函数,然后 2) 使用常规的 data.table 调用。

fx_make_glm = function(drug, treat_rate, control, Comparator_Rates)
  glm(formula = c(rbinom(drug, 1L, treat_rate),
                  rbinom(control, 1L, Comparator_Rates)) ~
        factor(c(rep("Trt", drug), rep("Cont", control))), 
      family = "binomial")

这将大大简化其余部分 - 我将使用 Map 它将遍历感兴趣的变量的每个元素:

scen_sims[, glm := list(Map(fx_make_glm, n, rate1, n, rate2))]

不幸的是,这仍然没有提供理想的性能:(

Unit: seconds
     expr  min   lq mean median   uq  max neval
  OP_loop 3.01 3.21 3.21   3.22 3.26 3.36     5
 map_call 2.64 2.89 2.90   2.92 2.96 3.08     5

我选择的并行包是 future.apply - 只需将 future_ 放在您的 *apply 系列前面,您就有了并行评估:

library(future.apply)
plan(multiprocess)
system.time(
  scen_sims[, glm := list(future_Map(fx_make_glm, n, rate1, n, rate2))]
)

   user  system elapsed 
   1.22    0.13    3.22 

## truncated the microbenchmark call

Unit: seconds
            expr  min   lq mean median   uq  max neval
         OP_loop 2.93 2.98 3.08   3.00 3.18 3.32     5
        map_call 2.65 2.70 2.94   2.89 3.18 3.25     5
 future_map_call 2.84 3.24 3.37   3.43 3.49 3.85     5

我在具有 2 核 / 4 线程的 Windows 上。如果我在 Linux 上,我会尝试plan(multicore) 看看分叉进程是否更有效率。

数据生成:

library(data.table)
## generate data
scen_bin <- expand.grid(n = c(10, 20, 30), rate1 = c(0.1, 0.2, 0.3),
                        rate2 = c(0.5, 0.6, 0.9))

rep <- 50L
scen_sims <- rbindlist(replicate(rep, scen_bin, simplify = FALSE),
                       idcol = TRUE)
scen_sims[, `:=`(glm, list(c(1L, 2L)))]

【讨论】:

嗯.. 谢谢。我使用 set 是因为我看到它非常有效。我仍然对为什么与多核并行会导致性能下降感到困惑。我真的很想知道,并行化此代码的最快方法。如果运行 100 个 glms 需要 6 秒,我希望使用 100 个内核我应该能够在 10 秒内运行 10000 个? 我也在 Windows 上。如果我在 linux 上,我觉得这会更有成效。您是否尝试过代码?关于set,您使用该部分很好。是 DF$var[i] 正在扼杀性能。见编辑 - 我做了一个更好的 data.table 方法。

以上是关于dopar中代码的流程优化的主要内容,如果未能解决你的问题,请参考以下文章

《爬取知网文献信息》中代码的一些优化

怎样查看DSP程序中代码的运行时间,该如何处理

Tutorial中代码的区别及不同效果

企业流程优化方法

关于网易云验证码的服务介绍

6.流程类优化