`mclapply`和`foreach()`循环工作过程的区别

问题描述 投票:0回答:1

这是出于好奇而提出的一般性问题。我正在使用

doParallel
包进行并行计算。我使用这些包来进行模拟。

我观察到,当我使用

foreach
循环进行模拟时,Rstudio 中的当前使用内存急剧上升(4+GiB),并且 Rstudio 有时崩溃了。

现在我转向

parallel::mclapply
并再次进行了相同的模拟,但令人惊讶的是没有问题,并且当前使用内存没有增加太多(10+MiB)。

我不明白代码内部发生了什么。我期待对上述过程的详细解释。

sessionInfo()
我的 R 是

R version 4.2.1 (2022-06-23) -- "Funny-Looking Kid"
Copyright (C) 2022 The R Foundation for Statistical Computing
Platform: aarch64-apple-darwin20 (64-bit)

操作系统是MacOS。

doParallel
软件包版本 1.0.17.

RStudio 版本 2023.03.01。

示例:

假设我们正在尝试计算 Erdos-Renyi 图的边数。我试图每次模拟图形并存储每次模拟的边计数值。

代码如下

#ER random graph generator
src1 <- {"#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericMatrix ER_AdjMatGEN_cpp(int N, double p){
  NumericMatrix temp(N,N);
  for(int i=0; i< N; i++){
    for(int j=0; j < i; j++){
        temp(i,j) = R::rbinom(1,p);
        temp(j,i) = temp(i,j);
    }
  }
  return temp;
}"}
Niter <- 10000
#________________
edgeCnt_result1 <- foreach(icount(Niter),
                           v = iter(function() ER_AdjMatGEN_cpp(10000, 0.3)),
                           .combine = "rbind") %dopar% {
                             sum(v)
                           }
#___________
edgeCnt_result1 <- do.call(rbind, mclapply(1:Niter, function(i) {v = ER_AdjMatGEN_cpp(N = 10000, 0.3) 
return(sum(v))} , mc.cores = 7))

当我尝试运行第一次迭代时,Rstudio 崩溃,但当我运行第二次迭代时,它运行正常。

r parallel-processing parallel.foreach doparallel mclapply
1个回答
0
投票

您观察到的

foreach()
mclapply()
之间的行为差异是由于它们处理内存和并行化的方式造成的。

  1. foreach()
    :R 中
    foreach()
    包中的
    doParallel
    函数是一个循环构造,提供 R 中标准
    for
    循环的并行版本。它使用
    doParallel
    包提供的并行后端,这是
    foreach
    包的“并行”适配器。
    doParallel
    包使用
    parallel
    包,它是基础 R 的一部分,提供并行执行功能。当您使用
    foreach()
    时,每个工作进程都会获得自己的数据副本。正如您所观察到的,如果您拥有大型数据集,这可能会导致内存使用量较高。

  2. mclapply()
    :R 中
    mclapply()
    包中的
    parallel
    函数在列表或向量上并行应用函数。它使用分叉进程来实现并行性,这是类 Unix 系统(包括 MacOS 和 Linux)上可用的方法。当您使用
    mclapply()
    时,分叉进程与父进程共享相同的内存空间,这可以导致较低的内存使用量。但是,这也意味着子进程中所做的任何更改都不会影响父进程。

在您的情况下,

foreach()
循环正在为每个工作进程创建数据副本,导致内存使用率较高并导致 RStudio 崩溃。另一方面,
mclapply()
使用共享相同内存空间的分叉进程,从而降低内存使用量。

这里有一些建议:

  • 如果您想继续使用
    foreach()
    ,请考虑减少每个工作进程需要处理的数据大小。
  • 如果您想继续使用
    mclapply()
    ,请注意它使用分叉进程,这可能存在一些限制和潜在问题,特别是当您在子进程中修改数据时。
  • 考虑使用 R 中的其他并行计算工具,例如
    future
    furrr
    包,它们为并行计算提供更灵活、更强大的选项。

以下是如何在 R 中使用

foreach()
mclapply()
的一些示例:

# Load the necessary libraries
library(doParallel)
library(parallel)

# Register the parallel backend for foreach
cl <- makeCluster(detectCores())
registerDoParallel(cl)

# Example using foreach
result_foreach <- foreach(i = 1:10, .combine = c) %dopar% {
  # Some computation here
  sqrt(i)
}

# Stop the cluster
stopCluster(cl)

# Example using mclapply
result_mclapply <- mclapply(1:10, function(i) {
  # Some computation here
  sqrt(i)
}, mc.cores = detectCores())

在这两个示例中,我们并行地将平方根函数应用于数字 1 到 10。

foreach()
示例使用
doParallel
包来设置并行后端,而
mclapply()
示例直接使用
parallel
包。

后续步骤可能包括:

  • 通过这些示例进行实验,了解
    foreach()
    mclapply()
    的工作原理。
  • 将这些示例改编为您自己的代码。
  • 探索 R 中的其他并行计算工具,例如
    future
    furrr
    包。
© www.soinside.com 2019 - 2024. All rights reserved.