【问题标题】:How can I reduce the time foreach take before and after multithreadedly going over the iterations?如何减少多线程迭代之前和之后的 foreach 时间?
【发布时间】:2017-07-19 00:18:02
【问题描述】:

我在R中使用foreach + doParallel对矩阵的每一行多线程应用一个函数。当矩阵有很多行时,foreach前后需要很长时间多线程遍历迭代。

例如,如果我运行:

library(foreach)
library(doParallel)

doWork <- function(data) {

  # setup parallel backend to use many processors
  cores=detectCores()
  number_of_cores_to_use = cores[1]-1 # not to overload the computer
  cat(paste('number_of_cores_to_use:',number_of_cores_to_use))
  cl <- makeCluster(number_of_cores_to_use) 
  clusterExport(cl=cl, varlist=c('ns','weights'))
  registerDoParallel(cl)
  cat('...Starting foreach initialization')

  output <- foreach(i=1:length(data[,1]), .combine=rbind) %dopar% {
    cat(i)
    y = data[i,5]
    a = 100
    for (i in 1:3) { # Useless busy work
      b=matrix(runif(a*a), nrow = a, ncol=a)
    }
    return(runif(10))

  }
  # stop cluster
  cat('...Stop cluster')
  stopCluster(cl)

  return(output)
}

r = 100000
c = 10
data = matrix(runif(r*c), nrow = r, ncol=c)
output = doWork(data)
output[1:10,]

CPU使用率如下(100%表示所有核心都被充分利用):

带注释:

如何优化代码以使foreach 在多线程迭代之前和之后不需要很长时间?主要时间槽是之后花费的时间。 after 所花费的时间随着 foreach 迭代次数的增加而显着增加,有时会使代码变慢,就像使用了简单的 for 循环一样。


另一个例子(假设lmpoly 不能将矩阵作为参数):

library(foreach)
library(doParallel)

doWork <- function(data,weights) {

  # setup parallel backend to use many processors
  cores=detectCores()
  number_of_cores_to_use = cores[1]-1 # not to overload the computer
  cat(paste('number_of_cores_to_use:',number_of_cores_to_use))
  cl <- makeCluster(number_of_cores_to_use) 
  clusterExport(cl=cl, varlist=c('weights'))
  registerDoParallel(cl)
  cat('...Starting foreach initialization')

  output <- foreach(i=1:nrow(data), .combine=rbind) %dopar% {
    x = sort(data[i,])
    fit = lm(x[1:(length(x)-1)] ~ poly(x[-1], degree = 2,raw=TRUE), na.action=na.omit, weights=weights)
    return(fit$coef)
  }
  # stop cluster
  cat('...Stop cluster')
  stopCluster(cl)

  return(output)
}

r = 10000 
c = 10
weights=runif(c-1)
data = matrix(runif(r*c), nrow = r, ncol=c)
output = doWork(data,weights)
output[1:10,]

【问题讨论】:

  • 我认为问题出在rbindrbind 列表中的大量值需要很长时间。此外,填充行是不好的,因为矩阵是按列存储的。此外,制作长 foreach 循环效率不高(改用块)。最后,在矩阵上并行化时,使用共享内存总是更好。如果您提出一个与您想要的更接近的示例,我可以为您制定解决方案。
  • 问题在于认为foreach %dopar%总是比矢量化方法快。 foreach %dopar% 需要在工作人员的 100,000 实例之间进行通信,因为您正在迭代 i=1:length(data[,1])data 有 100,000 行。在实施并行化方法之前,您应该在矢量化方法(sapply、lapply、apply)和并行化方法之间进行基准测试。如果您想坚持使用并行化方法,请将您的代码更改为处理列(其中有 10 个)而不是行。最好是让更少的工人开始,每个人都做更多的工作。
  • @F.Privé 谢谢,我会很感兴趣。我添加了一个与我想要的更接近的示例。
  • x = sort(data[i,])x[1:(length(x)-1)] 看起来很奇怪。你能确认这是你想做的吗?
  • @F.Privé 已确认。感谢您的精彩回答!

标签: r multithreading foreach


【解决方案1】:

试试这个:

devtools::install_github("privefl/bigstatsr")
library(bigstatsr)
options(bigstatsr.ncores.max = parallel::detectCores())

doWork2 <- function(data, weights, ncores = parallel::detectCores() - 1) {

  big_parallelize(data, p.FUN = function(X.desc, ind, weights) {

    X <- bigstatsr::attach.BM(X.desc)

    output.part <- matrix(0, 3, length(ind))
    for (i in seq_along(ind)) {
      x <- sort(X[, ind[i]])
      fit <- lm(x[1:(length(x)-1)] ~ poly(x[-1], degree = 2, raw = TRUE), 
               na.action = na.omit, weights = weights)
      output.part[, i] <- fit$coef
    }

    t(output.part)
  }, p.combine = "rbind", ncores = ncores, weights = weights)
}

system.time({
  data.bm <- as.big.matrix(t(data))
  output2 <- doWork2(data.bm, weights)
})

all.equal(output, output2, check.attributes = FALSE)

这在我的计算机(只有 4 个内核)上快两倍。备注:

  • 使用超过 一半 的内核通常是没有用的。
  • 您的数据不是很大,因此在这里使用big.matrix 可能没有用处。
  • big_parallelize 将矩阵分隔在 ncores 列块中,并将您的函数应用于每个列,然后合并结果。
  • 在函数中,最好在循环之前做出输出,然后填充它,而不是使用foreachrbind 所有结果。
  • 我只访问列,而不是行。

因此,所有这些都是很好的做法,但它与您的数据并不真正相关。当使用更多内核和更大的数据集时,增益应该更高。

基本上,如果你想超快,在 Rcpp 中重新实现 lm 部分将是一个很好的解决方案。

【讨论】:

    【解决方案2】:

    正如评论中提到的 F. Privé:

    我认为问题出在 rbind 上。 rbind 列表中的许多值需要很长时间。此外,填充行是不好的,因为矩阵是按列存储的。此外,制作长 foreach 循环效率不高(改用块)。

    改为使用 use 块(如果使用 5 个核心,每个核心获得 20% 的矩阵):

    library(foreach)
    library(doParallel)
    
    
    array_split <- function(data, number_of_chunks) {
      # [Partition matrix into N equally-sized chunks with R](https://stackoverflow.com/a/45198299/395857)
      # Author: lmo
      rowIdx <- seq_len(nrow(data))
      lapply(split(rowIdx, cut(rowIdx, pretty(rowIdx, number_of_chunks))), function(x) data[x, ])
    }
    
    
    doWork <- function(data) {
    
      # setup parallel backend to use many processors
      cores=detectCores()
      number_of_cores_to_use = cores[1]-1 # not to overload the computer
      cat(paste('number_of_cores_to_use:',number_of_cores_to_use))
      cl <- makeCluster(number_of_cores_to_use) 
      clusterExport(cl=cl, varlist=c('ns','weights'))
      registerDoParallel(cl)
    
      cat('...Starting array split')
      number_of_chunks = number_of_cores_to_use
      data_chunks = array_split(data=data, number_of_chunks=number_of_chunks)
      degree_poly = 2
    
      cat('...Starting foreach initialization')
      output <- foreach(i=1:length(data_chunks), .combine=rbind) %dopar% {
    
        data_temporary = data_chunks[[i]]
        output_temporary = matrix(0, nrow=nrow(data_temporary), ncol = degree_poly + 1)
        for(i in 1:length(data_temporary[,1])) {
          x = sort(data_temporary[i,])
          fit = lm(x[1:(length(x)-1)] ~ poly(x[-1], degree = degree_poly,raw=TRUE), na.action=na.omit, weights=weights)
          output_temporary[i,] = fit$coef
        }
        return(output_temporary)
      }
    
      # stop cluster
      cat('...Stop cluster')
      stopCluster(cl)
    
      return(output)
    }
    
    r = 100000
    c = 10
    weights=runif(c-1)
    data = matrix(runif(r*c), nrow = r, ncol=c)
    output = doWork(data)
    output[1:10,]
    

    仅供参考:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-17
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多