RNote101---多线程处理

本文探讨了R语言中利用snow和foreach包实现多线程处理的方法,通过实例对比了不同核心数量下的程序运行时间,展示了多线程在提高R语言处理效率上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  R的循环效率比较慢,通常可以使用apply族函数进行加速,那么多线程怎么整?只介绍方法,原理不是很懂。

R会给出程序运行时间,供参考:

  • 用户:是消耗在应用程序(非操作系统部分)执行的时间
  • 系统:是底层操作系统执行(例如磁盘读写等)部分的时间
  • 流逝:是经过的总时间(可以认为是前两者的总和)

snow包

先查看电脑有几个core
我现有电脑配置是6核,逻辑处理器是12

parallel::detectCores()

12

# 不设置多线程  
vec <- 1:10000000
cat("-------- vapply执行 --------")
system.time(vapply(vec,function(x) x+2,FUN.VALUE=0))
# 开启多进程
cat("-------- Core设置为5 --------")
clus <- snow::makeCluster(5)
system.time(snow::parLapply(clus,vec, function(x){x+2}))
cat("-------- Core设置为10 --------")
clus <- snow::makeCluster(10)
system.time(snow::parLapply(clus,vec, function(x){x+2}))
# 关闭多进程
snow::stopCluster(clus)
-------- vapply执行 --------


   user  system elapsed 
   4.50    0.03    4.54 


-------- Core设置为5 --------


   user  system elapsed 
   2.14    0.44    6.58 


-------- Core设置为10 --------


   user  system elapsed 
   2.11    0.25    4.99 

**可以看到做了多线程之后,user时间变少了。**但是很多时候我们的处理过程并不是简单的向量apply操作,比如做分析时,会有多个SQL需要查询,这些SQL本身独立,也可多线程的操作,该如果处理呢?看个Demo:

  • 有一个数据框,两列(name,value)
  • 要求做groupby操作进行sum求和,name作为参数多线程计算

构造数据

demoDf <- data.frame(name=sample(letters,100000,replace = TRUE),value=seq(1,100000))

进行groupby的操作

demofunction <- function(name,df=demoDf){
  subDf <- subset(df,name==name)
  return(data.frame(name=name,sum=sum(subDf$value)))
  }
system.time(expr = {
    clus <- snow::makeCluster(10)
    # 提交多线程用到的变量&函数
    snow::clusterExport(clus, c("demofunction", "demoDf"))
    res <- snow::parLapply(clus, letters, function(x) demofunction(name = x, df = demoDf))
    # 关闭多进程
    snow::stopCluster(clus)
    resDf <- do.call(rbind, res)
})
   user  system elapsed 
   0.01    0.00    1.69 
head(resDf,3)
A data.frame: 3 × 2
namesum
<fct><dbl>
a5000050000
b5000050000
c5000050000

foreach和doParallel

  snow包的用法像是apply族的并行化,foreach和doParallel更像是对for循环的并行化。还是那句话原理不是很懂,看代码~

library(foreach)
# library(doParallel)
eig <- function(n, p){
x <- matrix(rnorm(100000), ncol=100)
r <- cor(x)
eigen(r)$values
}
n <- 1000000
p <- 100
k <- 500
cat("-------- 不并行 --------")
# 不并行
system.time(
x <- foreach(i=1:k, .combine=rbind) %do% eig(n, p)
)
cat("-------- Core设置为2 --------")
cl<- snow::makeCluster(2)      # 注册线程,可以是detectCores()返回的核数。
## 登记内核数量  
doParallel::registerDoParallel(cl)
system.time(
y <- foreach(i=1:k, .combine=rbind) %dopar% eig(n, p)
)
snow::stopCluster(cl) # 结束集群

cat("-------- Core设置为5 --------")
cl<- snow::makeCluster(5)      # 不过R中能运行满线程 ,即detectCores()返回的核数。
## 登记内核数量  
doParallel::registerDoParallel(cl)
system.time(
y <- foreach(i=1:k, .combine=rbind) %dopar% eig(n, p)
)
snow::stopCluster(cl) 

cat("-------- Core设置为10 --------")
cl<- snow::makeCluster(10)      # 不过R中能运行满线程 ,即detectCores()返回的核数。
## 登记内核数量  
doParallel::registerDoParallel(cl)
system.time(
y <- foreach(i=1:k, .combine=rbind) %dopar% eig(n, p)
)
snow::stopCluster(cl) 
Warning message:
"package 'foreach' was built under R version 3.6.3"

-------- 不并行 --------


   user  system elapsed 
   6.22    0.00    6.21 


-------- Core设置为2 --------


   user  system elapsed 
   0.14    0.06    3.38 


-------- Core设置为5 --------


   user  system elapsed 
   0.06    0.02    1.47 


-------- Core设置为10 --------


   user  system elapsed 
   0.20    0.03    1.04 

从系统总耗时看,所耗费时间是降低的。再看一个简单的例子:

system.time(expr = {
    foreach(i = 1:4) %do% {
        Sys.sleep(5)
        i
    }
})
   user  system elapsed 
   0.00    0.00   20.03 
cl<- snow::makeCluster(5)      # 不过R中能运行满线程 ,即detectCores()返回的核数。
## 登记内核数量  
doParallel::registerDoParallel(cl)
system.time(expr = {
    foreach(i = 1:4) %dopar% {
        Sys.sleep(5)
        i
    }
})
snow::stopCluster(cl) 
   user  system elapsed 
   0.02    0.00    5.03 

当不并行时,sleep5s串行了4次,共计20s;并行之后相当于在不同线程同时sleep一次,5s;

snow和foreach

system.time(expr = {
    clus <- snow::makeCluster(5)
    # 提交多线程用到的变量&函数
    snow::clusterExport(clus, c("eig", "k","p"))
    res <- snow::parLapply(clus, 1:k, function(x) eig(n, p))
    # 关闭多进程
    snow::stopCluster(clus)
    resDf <- do.call(rbind, res)
})
   user  system elapsed 
   0.01    0.02    2.40 
head(resDf)
A matrix: 6 × 100 of type dbl
1.6561001.6406371.6167981.5868661.5669301.5383741.5230151.4950041.4888971.468040...0.58629090.57620280.57414230.56449110.53840940.53636960.52351160.51597770.49646010.4866267
1.6912351.6583951.6097771.5750121.5583771.5394801.5228831.4965921.4893861.458325...0.58962760.58738410.57379160.57221870.56117330.55794170.52852590.52173620.51536330.4782889
1.6864251.6531211.6281791.5985511.5796561.5574331.5372281.4987531.4899751.460616...0.59520690.58231800.57202120.56810740.55519170.54502210.53194060.52681100.51734170.5001969
1.7138291.6762941.6136051.5799711.5672251.5457871.5194951.4943731.4714821.467215...0.59461690.58530680.57191700.55025860.54985090.54351400.53584130.51120630.49372760.4767415
1.6779331.6157441.5940951.5777021.5615101.5443891.4991721.4914931.4711601.449174...0.60707640.60248970.58991970.57072630.55914820.54215570.52434880.51724210.50340020.4930905
1.6590661.6299821.6125701.5827311.5702511.5358481.5177821.5074631.4949941.487952...0.59876510.58329030.57457900.55771870.54949240.54325000.53786300.51284770.50624550.4900256
# library(doParallel)
cl<- snow::makeCluster(5)      # 不过R中能运行满线程 ,即detectCores()返回的核数。
## 登记内核数量  
doParallel::registerDoParallel(cl)
system.time(expr={x <- foreach(i=1:1000)%dopar%function(x){x+2}})
doParallel::stopImplicitCluster()
snow::stopCluster(cl) 
   user  system elapsed 
   0.87    0.31    1.19 

上面简单的测试,发现向量化的简单操作snow似乎优势更大,foreach循环次数到10000000电脑基本卡住了。如果是做复杂计算,似乎foreach更合适些。实际应用可以做个测试看看~
但是snow有个挺麻烦的事儿,需要把用到的变量和函数clusterExport,这个操作挺不人性化的。实际中用过来,感觉还是foreach的并行化处理更方便些。

Ref

[1] R语言实战第二版P444
[2] https://zhuanlan.zhihu.com/p/31998150
[3] https://blog.csdn.net/wa2003/article/details/48145147
[4] https://zsccy.xyz/md/2018-04-08-r%E8%AF%AD%E8%A8%80%E5%B9%B6%E8%A1%8C%E5%8C%96%E8%AE%A1%E7%AE%97%E4%B9%8Bforeach%E5%8C%85/

                                2020-04-22 于南京市江宁区九龙湖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值