[笔记]提高R的性能和突破内存限制的技巧

本文为雪晴数据网《R语言大规模数据分析实战》 http://www.xueqing.tv/course/56 的课程学习笔记。html

该课程目前更新到“第2章 Microsoft R Server简介”的微软数据科学家介绍MRS,后续教学主要是关于MRS的内容,再另外学习,因此本文只学习“第1章 提高R的性能和突破内存限制的技巧”git

1. 课程介绍

1.1 课程目录

第一章 突破R内存瓶颈的一些小技巧github

  • 升级硬件和软件web

  • 减小数据复制redis

  • 利用整数的优点算法

  • 有效地存储数据数据库

  • 在转换数据的时候避免循环windows

  • 在关键函数里使用C、C++或Fortran后端

  • 尽量地使用面向行的数据转换服务器

  • 排序以前要三思

  • 使用bigmemory家族的包

  • 借助数据库

  • 使用Revolution R Enterprise(简称RRE)

第二章 RRE的简介

  • RRE学术版的下载与安装

  • RRE的功能介绍

  • 导入数据的函数

  • 归纳数据的函数

  • RRE可视化功能

  • RRE所支持的算法介绍

第三章 用RRE作数据探索

  • 导入数据

  • 用rxGetVarInfo()函数查看数据的基本特征

  • 用rxSummary()函数计算数据的描述统计量

  • 用rxHistogram()分析数据的分布

  • 用rxLinePlot()可视化分析两个变量之间的关系

  • 用rxCrossTabs()分析变量间的关系

  • 用with rxCube()分析变量间的关系

第四章 用RRE作数据整理

  • 数据融合

  • 用rxDataStep()作数据变换

  • 用dplyrXdf包整理数据

第五章 用RRE作数据挖掘(案例实战)

  • 数据准备

  • 导入数据

  • 数据探索

  • 线性回归

  • 逻辑回归

  • K-means聚类

  • 决策树分类

1.2 课程视频目录

第1章:提高R的性能和突破内存限制的技巧

  • 如何提高R的性能

  • 并行计算

第2章:Microsoft R Server简介

  • 微软数据科学家介绍MRS

2. 第1章:提高R的性能和突破内存限制的技巧

2.1 如何提高R的性能

这一节先介绍提升R性能的几种方法,而后重点介绍如何利用R的内部机制来提高性能。

2.1.1 性能提高的方法

a.1 系统升级

  • 升级硬件

  • 使用64位操做系统

  • 利用GPU

  • 租用云计算服务器

a.2 开发层面的优化

  • 算法
    下降算法复杂度

  • 调用C/C++或者Fortran
    关键的、耗时的计算步骤

  • 缓冲技术
    减小重复计算

a.3 使用层面的优化

  • 充分利用R的内存机制——R的基础优化

  • 加强R的矩阵运算——加速BLAS

  • 并行计算

  • 大规模数据的处理——图片内存限制

  • 使用Revolution R Enterprise(RRE)

下列介绍经过充分利用R的内部机制优化性能

2.1.2 向量化

向量化的代码,不要用循环!

  • 利用矩阵运算

  • 利用内置的向量化函数,好比exp、sin、rowMeans、rowSums、colSums、ifelse等

  • 利用Vectorize函数将非向量化的函数改装为向量化的函数

  • *apply函数族:apply、lapply、sapply、tapply、mapply等

  • plyr和dplyr包
    Rstudio发布的data wrangling cheat sheet

##利用矩阵运算
n <- 100000
x1 <- 1:n
x2 <- 1:n
y <- vector()
system.time(
    for(i in 1:n){y[i] <- x1[i] + x2[i]}
)
system.time(y <- x1 + x2)

## 利用向量化运算
## 内置的向量化函数
v <- 1:100000
result <- rep(1:100000)
system.time(
    for(i in 1:100000){result[i] <- sin(v[i])}
)
system.time(result <- sin(v))

## 利用rowMeans、rowSums、colSums、colMeans等函数对矩阵或数据库作总体处理
colSums(iris[,1:4])

利用R内置的向量化函数,自定义向量化函数,只要在函数定义时每一个运算是向量化的。可是在函数定义时用了逻辑判断语句,就会破坏的向量化特征。

func <- function(x){
    if(x %% 2 == 0){
        ret <- TRUE
    }else{
        ret <- FALSE}
    return(ret)
}
func(34)
func(c(1,2,3,4))
## Warning message:
## In if (x%%2 == 0) { :
##   the condition has length > 1 and only the first element will be used
## 在函数的定义中有if语句,不能接受向量做为判断的条件,不然判断第一个元素。

## 利用ifelse函数作向量化的判断
myfunc <- function(x){
    ifelse(x %% 2 == 0,TRUE,FALSE)
}
myfunc(c(1,2,3,4))

##利用Vectorize函数将非向量化的函数改装为向量化的函数
funcv <- Vectorize(func)
funcv(c(1,2,3,4))

##利用sapply函数向量化运算
sapply(c(1,2,3,4),func)

2.1.3 预先给对象分配内存

R为解释性语言,也是动态语言,若是不事先指定对象的类型和长度,在运算过程会动态分配内存,提升灵活性,但下降了效率。

尽可能减小cbind、rbind的使用

## 求出10000个斐波那契数
x <- c(1,1)
i <- 2
system.time(
    while(i<10000){
        new <- x[i] + x[i-1]
        x <- cbind(x,new)
        i <- i + 1
    }
)

## 指定类型和长度
x <- vector(mode="numeric",100000)
x[1] <- 1
x[2] <- 1
system.time(
    while(i<10000){
        i <- i + 1
        x[i] <- x[i-1] + x[i-2]
    }
)

2.1.4 避免内存拷贝

假设咱们有许多彼此不相关的向量,但由于一些其余的缘由,咱们但愿将每一个向量的第三个元素设为8,既然它们是互不相关的,甚至可能具备不一样的长度,咱们也许会考虑将它们放在一个列表中:

m <- 5000
n <- 1000
z <- list()
for(i in 1:m) z[[i]] <- sample(1:10, n, replace = T)
system.time(for(i in 1:m) z[[i]][3] <- 8)

## 把这些向量一块儿放到矩阵中
z <- matrix(sample(1:10, m * n, replace = T),nrow = m)
system.time(z[,3] <- 8)

2.1.5 删除临时对象和再也不用的对象

  • rm()删除对象
    rm(object)删除指定对象,rm(list = ls())能够删除内存中的全部对象

  • gc()内存垃圾回收
    使用rm(object)删除变量,要使用gc()作垃圾回收,不然内存是不会自动释放的。invisible(gc())不显示垃圾回收的结果

2.1.6 分析内存的函数

  • ls()列出特定环境中的对象

  • object.size()返回R对象的大小(近似的)

  • memory.profile()分析cons单元的使用状况

  • memory.size()监测所有内存的使用状况(仅Windows下可用)
    memory.size(max=T)返回历史占用过的最大内存;memory.size(max=F)返回目前占用的内存。未作垃圾清理时,已使用内存和已分配内存同步增长,但在垃圾清理后rm(list=ls());gc(),已使用内存会减小,而已分配给R的内存不会改变。

  • memory.limit()系统可分配的内存上限(仅Windows下可用)
    memory.limit(newLimit)更改到一个新的上限。 注意,在32位的R中,封顶上限为4G,你没法在一个程序上使用超过4G (数位上限)。这种时候,能够考虑使用64位的版本。

2.2 并行计算

本节主要介绍parallel包,后续介绍R与Hadoop的结合

2.2.1 parallel包

parallel包实际上整合了以前已经比较成熟的snow包和multicore包,multicore没法在windows下运行。

## 一个简单的例子
system.time(for(i in 1:4){Sys.sleep(2)})
## 或者用lapply改写成:
system.time(lapply(1:4, function(i) Sys.sleep(2)))
## 设置并行环境
library(parallel)
## 检测系统可用的核数
detectCores()
## 默认返回的结构逻辑的核数,需修改logical=FALSE,返回物理核数
detectCores(logical=FALSE)
## 创建2核的集群
cl <- makeCluster(2)

## 不使用并行计算
system.time(lapply(1:4, function(i) Sys.sleep(2)))

## 使用parallel包,运行时间减半
## 在非windows系统下,使用mclapply函数
system.time(
    mclapply(1:4, function(i) Sys.sleep(2),mc.cores=2)
)
## 在windows系统下,使用parlapply函数
system.time(
    parlapply(cl, 1:4 function(i) Sys.sleep(2))
)

##关闭集群
stopCluster(cl)

2.2.2 foreach和doParallel包

使用parallel包进行并行运算时,需改写原来的程序,且改写较多。

foreach包是一个并行计算的框架:循环控制+并行执行。foreach至关于for的延伸,在循环的过程当中它可以选择不一样的并行后段进行执行。在非并行运算过程当中代替for。改写为并行运算,改写不多。

## 一个简单的例子
## 不并行的版本
library(foreach)
foreach(i=1:4) %do% sqrt(i)
## .combine则表示运算结果的整合方式,.combine='c'运算结果为向量
foreach(i=1:4,.combine='c') %do% sqrt(i)
system.time(foreach(i=1:4) %do% sqrt(i))
## 并行的版本,须要把%do%改成%dopar%
system.time(foreach(i=1:4) %dopar% sqrt(i))
## 并行计算失败!

用foreach作并行计算必须跟并行后端(parallel backend)配合使用

doParallel包时foreach包执行并行计算时的后端接口程序。

CRAN上还有如下的并行计算后端包:

  • doMPI与Rmpi包配合使用

  • doRedis与rredis包配合使用

  • doMC提供parallel包的多核计算接口

  • doSNOW提供现已废弃的SNOW包的接口

library(foreach)
## 注册并行后端
library(doParallel)
cl <- makeCluster(2)
registerDoParallel(cl)
## 使用doParallel包作foreach的并行后端
system.time(foreach(1:4) %dopar% Sys.sleep(2))

system.time(for(i in 1:400){sqrt(i)})
system.time(lapply(1:400, function(i) sqrt(i)))
system.time(foreach(i=1:400) %do% sqrt(i))
system.time(foreach(i=1:400) %dopar% sqrt(i))
system.time(foreach(i=1:400,.combine='c') %dopar% sqrt(i))
## foreach和doParallel并行计算速度比不并行计算还慢不少

## 关闭集群
stopCluster(cl)

案例

library(parallel)
cl <- makeCluster(2)
fun <- function(x){
    return(x+1)
}
## 不并行计算效果
system.time(
    res <- lapply(1:5000000, fun)
)
## parallel并行计算效果
system.time(
    res <- mclapply(1:5000000, fun, mc.cores=2)
)
## foreach并行计算效果
library(foreach)
library(doParallel)
registerDoParallel(cl)
system.time(
    res <- foreach(x=1:5000000,.combine='cbind') %dopar%
        fun(x)
)

## 关闭集群
stopCluster(cl)

parallel并行计算速度比不并行计算速度稍微快点,而foreach并行计算速度比不并行计算还慢不少不少,不知道是什么缘由

不知何故,在Mac OSX 下采用doParallel速度特别慢, 比不用并行还慢,因而又尝试了doMC

library(doMC)
## 设置并行核数, 并注册并行
registerDoMC(2)
## 开始计算
system.time(foreach(i=1:4) %dopar% sqrt(i))

## 比较不并行计算和doParallel并行计算
system.time(foreach(i=1:4) %do% sqrt(i))
cl <- makeCluster(2)
registerDoParallel(cl)
system.time(foreach(i=1:4) %dopar% sqrt(i))

doMC比doParallel还慢!

Getting Started with doParallel and foreach的解释以下:

With small tasks, the overhead of scheduling the task and returning the result can be greater than the time to execute the task itself, resulting in poor performance. In addition, this example doesn’t make use of the vector capabilities of sqrt, which it must to get decent performance. This is just a test and a pedagogical example, not a benchmark.

对于小任务,调度任务并返回结果的开销可能比执行任务自己还耗时,致使表现不佳。

下列为实际的例子

x <- iris[which(iris[,5] != "setosa"), c(1,5)]
trials <- 10000
ptime <- system.time({
    r <- foreach(icount(trials), .combine=cbind) %dopar% {
    ind <- sample(100, 100, replace=TRUE)
    result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
    coefficients(result1)
    }
})

stime <- system.time({
    r <- foreach(icount(trials), .combine=cbind) %do% {
    ind <- sample(100, 100, replace=TRUE)
    result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
    coefficients(result1)
    }
})
ptime
stime

3. 参考资料

相关文章
相关标签/搜索