如何在 R 中嵌套 foreach 循环的内循环和外循环之间添加代码

Posted

技术标签:

【中文标题】如何在 R 中嵌套 foreach 循环的内循环和外循环之间添加代码【英文标题】:How can I add code between the inner and outer loops of nested foreach loops in R 【发布时间】:2021-08-06 07:39:15 【问题描述】:

我了解到在 R 中执行嵌套 foreach 循环的正确方法是通过嵌套运算符 %:%(例如 https://cran.r-project.org/web/packages/foreach/vignettes/nested.html)。

但是,当使用这种方法时,不能在内循环和外循环之间添加代码 -- 请参见下面的示例。

有没有办法创建嵌套的、并行化的 foreach 循环,以便在内循环和外循环之间添加代码?

更一般地说,想到的明显方式有什么问题,即简单地使用 %dopar% 运算符而不是 %:% 运算符嵌套两个 foreach 循环?请参阅下面的简单示例。

library(foreach)

# Set up backend
cl = makeCluster(6)
registerDoParallel(cl)
on.exit(stopCluster(cl))

# Run nested loop with '%:%' operator. Breaks if adding code between the inner and outer loops 
foreach(i=1:2) %:% 
  # a = 1 #trivial example of running code between outer and inner loop -- throws error 
  foreach(j = 1:3) %dopar% 
    i * j
  

# Run nested loop using 2 '%dopar%' statements -- is there anything wrong with this?
foreach(i=1:2, .packages = 'foreach') %dopar% 
  a = 1 #trivial example of running code between outer and inner loop
  foreach(j = 1:3) %dopar% 
    i * j
  

【问题讨论】:

我会避免尝试运行嵌套的并行循环,应该并行化内部或外部循环,而不是同时并行化两者。如果你有 6 个核心并且所有 6 个核心都用于外循环,那么如何有任何剩余的核心来并行化内循环? 我猜你可以使用 R 包 future 中的期货循环来做到这一点。 【参考方案1】:

provided 文档中的“使用%:%%dopar%”一章给出了有用的提示:

所有的任务都是完全独立的,所以它们都可以并行执行

%:% 运算符将多个 foreach 循环变成一个循环这就是上面示例中只有一个%do% 运算符的原因。当我们通过将 %do% 更改为 %dopar% 来并行化嵌套的 foreach 循环时,我们正在创建一个可以并行执行的单个任务流。

当您结合两个%dopar% 并测量执行时间时,您会看到只有外部循环并行执行,这可能不是您要寻找的:

system.time(
foreach(i=1:2, .packages = 'foreach') %dopar% 
  # Outer calculation
  Sys.sleep(.5)
  foreach(j = 1:3) %dopar% 
    # Inner calculation
    Sys.sleep(1)
  
)
#  user      system     elapsed 
#  0.00        0.00        3.52 

这个经过的时间反映了:

parallel[ outer(0.5s) + sequential [3 * inner(1s)] ] ~ 3.5s

如果外部计算不是太长,将其放入内部循环实际上更快,因为使用了您示例的 6 个工作人员:

system.time(res <- foreach(i=1:2, .packages = 'foreach') %:%
  foreach(j = 1:3) %dopar% 
    # Outer calculation
    Sys.sleep(.5)
    # Inner calculation
    Sys.sleep(1)
  )
#  user      system     elapsed 
#  0.02        0.02        1.52 

如果外部计算太长并且您的内部循环比外部循环多得多,您可以并行预先计算外部循环。然后您可以在%:% 中使用结果:

system.time(
  precalc <- foreach(i=1:2) %dopar% 
    # Outer pre-calculation
    Sys.sleep(2)
    i
  
  foreach(i=1:2, .packages = 'foreach') %:%
    foreach(j = 1:12) %dopar% 
      # Inner calculation
      Sys.sleep(1)
      precalc[[i]]*j
    
)
#   user  system elapsed 
#   0.11    0.00    5.25 

比:

system.time(
  foreach(i=1:2, .packages = 'foreach') %:%
    foreach(j = 1:12) %dopar% 
      # Outer calculation
      Sys.sleep(2)
      
      # Inner calculation
      Sys.sleep(1)
      i*j
    
)

#   user  system elapsed 
#   0.13    0.00    9.21

【讨论】:

感谢@Waldi,这很有帮助。只想澄清两点。 1)当你说“当你结合两个 %dopar% 并测量执行时间时,你会看到只有外部循环是并行执行的”,你知道这是真的还是仅仅基于缓慢的执行时间? 2) 我的主要问题是是否有办法创建嵌套的、并行化的 foreach 循环,以便可以在内部和外部循环之间添加代码。我假设简短的答案是“不 - 你需要预先计算它”是否正确?非常感谢 @jruf03,关于问题1),这是事实。我专门将Sys.sleep(0.5) 用于外循环,Sys.sleep(1) 用于内循环,以便我们可以检查发生了什么:2 个内核并行运行 [outer(0.5s) + not parallel [3 * inner(1s)]] ~ 3.5 秒。即使我们增加外部循环的数量(最多 6 个可用内核),这个计算仍然成立。 关于问题 2),简短的回答是我没有管理 to use futures 在之前的 cmets 中建议的主进程内启动子进程。我认为这可行,但无法证明这一点,因此我采用了预计算解决方法,其副作用是明确并行化的关键是保持任务彼此独立

以上是关于如何在 R 中嵌套 foreach 循环的内循环和外循环之间添加代码的主要内容,如果未能解决你的问题,请参考以下文章

嵌套的 Foreach 循环花费了太多时间

如何在 Nightwatch 测试的自定义命令中添加嵌套函数 javascript - forEach - 循环遍历元素

在R中将嵌套的for循环转换为并行

如何打破嵌套的foreach循环然后转到c#上的父foreach循环

如何从嵌套的 ForEach 循环中获取连续的索引号?

如何在“R”中的foreach循环中导出多个函数或包