R语言性能Tips和GC

举报
吕小卒子 发表于 2019/09/23 13:50:21 2019/09/23
【摘要】 概述最近团队在使用R语言作为算法的实践语言,通过人工策略和xgboost算法进行一些价格算法的控制和输出,发现一些代码中对于内存、CPU、程序设计思想以及现代统计算法并不是很熟悉,于是特写此篇普及一下知识,也算是我对R语言的入门文章吧。GC对R的内存管理的充分理解将帮助您预测给定任务需要多少内存,并帮助您充分利用您拥有的内存。它甚至可以帮助您编写更快的代码,因为copy造成的副本是代码速度慢...

概述

最近团队在使用R语言作为算法的实践语言,通过人工策略和xgboost算法进行一些价格算法的控制和输出,发现一些代码中对于内存、CPU、程序设计思想以及现代统计算法并不是很熟悉,于是特写此篇普及一下知识,也算是我对R语言的入门文章吧。

GC

对R的内存管理的充分理解将帮助您预测给定任务需要多少内存,并帮助您充分利用您拥有的内存。它甚至可以帮助您编写更快的代码,因为copy造成的副本是代码速度慢的主要原因。希望博主的这篇博客可以帮助您理解R中的内存管理基础知识,从单个对象到函数,再到更大的代码块。 何为GC(garbage collection)?说白了,它是一种机制,确切的说:一种”垃圾”回收机制算法,垃圾是指无用或者长时间占用内存空间的垃圾对象(变量、函数或者类类型实例)。可以将计算机的内存粗略的分为:全局数据区、代码区、栈区和堆区栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等,但是R不会自动释放function内的临时变量的。堆区:动态内存申请与释放,按需驻留在内存区域,不用时需要释放掉,如不释放掉则会存在内存溢出、地址混淆等各种问题。

test<-function(){
	//向量存储在栈内存
	x<-c(1:10)
}
//或者你也可以通过s3或者s4创建class
setClass("Person", representation(name = "character", age = "numeric"))
//new创建的实例存储在堆内存
hadley <- new("Person", name = "Hadley", age = 31)
gc()

Vcells:向量,Ncells:其它所有,其中gc triger很重要感兴趣的读者可以了解一下。如下图所示:

1620

值得关注的是:R语言用的垃圾回收算法是分代算法,通过一些小技巧name属性来实现copy-on-write(是不是突然想到了Docker分层的copy),因为是分代回收,所以函数里的临时变量都不会马上删掉, 而且每次重复赋值, 上一次的数据依然存在于内存。函数或者什么东西创建的临时变量被释放后,R不会马上调用内存回收gc()函数,所以有时候看windows的任务管理器/Linux的top不能看出R内存变化。R会在内存不够用(要去读C代码)时自动调用gc释放内存。这一点和JAVA类似。这一点和编译语言C/Cpp有非常大的区别,后者要用户手动free或者析构(~Class())。如果不断操作一个占用内存很大的object, 会占用非常多的内存, 所以我们需要把不用的内存gc()释放掉。1.当name为0时, 没有任何object使用它,可以删掉.2.当name为1时, 正在有表达式在用它,所以复制了一份。3.当name为2时, 证明有另一个变量指向了它,当修改时要复制一份出来。我在学习R的GC机制中,看到某网友的封装了R-release function,例如:

r_release <- function(var){
        environment()
        print(tracemem(var))
      # unlockBinding("var", .BaseNamespaceEnv)
        #rm(var)
        print(eval(var))
        print(class(eval(var)))
        print(sprintf("%s", var))
        rm(list = eval(var), envir=parent.frame())
        gc(verbose = FALSE)
    }
a<-c(1:1000)
r_release("a")
输出为:Error: object 'a' not found

如何做

1.对于自己创建object时,分析清楚数据是不是经常使用常驻内存还是临时object。 2.对于object按值传递还是按引用传递分析清楚,并深入理解R的浅拷贝还是深拷贝。 3.利用R的一些性能和内存分析工具(lineprof、time和memory.profile()),对自己的代码进行分析和探索。 4.将业务和问题域的代码学会使用算法,不仅是机器学习算法还是传统的算法,将时间复杂度和空间复杂度降到最低。 5.能上Rcpp就Rcpp,对C要有信心,语言就是一种工具;学会使用MPI克服多进程的管理。去CRAN上寻找更快的包,例如:fastcluster,princomp,fastmatch,RcppEigen,data.table,dplyr。另外两点也很重要:利用compiler进行提前预编译,进而加快运行速度。在一个就是使用GPU让R运行的更快。6.养成良好的编程习惯(代码风格、注释、设计模式和深度思考的习惯即问题本质)。 暂时想到这些

实践

1.能用向量化就要用向量化(即矩阵),进而转化成线性代数求解和并行加速。

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

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

  • 函数族: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]}
)
#for time输出
user  system elapsed
0.032   0.005   0.037 
system.time(y <- x1 + x2)
#向量化时间占用
user  system elapsed 
0.000   0.000   0.001

user time:执行调用进程的用户态指令所占用的CPU时间。system time:内核态系统调用进程执行的CPU时间。elapsed time:约等于用户态+内核态时间我们能看到采用for循环时间是向量化矩阵时间37倍,并且在用户态和内核态的时间基本上是没有时间消耗。所以利用R内置的向量化函数,自定义向量化函数,只要在函数定义时每个运算是向量化的。(利用rowMeans、rowSums、colSums、colMeans等函数对矩阵或数据库做整体处理)。如果我们在函数定义时加了逻辑判断表达式会破坏向量化计算的。



test <- function(x){
    if(x %% 3 == 0){
        result <- TRUE
    }else{
        result <- FALSE}
    return(result)
}
test(12)
test(c(1:10))
#输出中会有Warning 警告
Warning message:
In if (x%%3 == 0) { :
  the condition has length > 1 and only the first element will be used

所以在学会利用函数ifelse、Vectorize和sapply转化向量化运算。

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

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

2.R是一门解释性动态语言,在运算过程会动态分配内存,提高灵活性,但降低了效率。所以要尽量避免使用bind技巧和内存copy,预先给对象分配内存。

## 求出10000个斐波那契数
x <- c(1,1)i <- 2system.time(
    while(i<10000){
        new <- x[i] + x[i-1]
        x <- cbind(x,new)
        i <- i + 1
    })## 指定类型和长度
x <- vector(mode="numeric",100000)x[1] <- 1x[2] <- 1system.time(
    while(i<10000){
        i <- i + 1
        x[i] <- x[i-1] + x[i-2]
    })

在使用bind和不使用bind的效果,计算结果如下图对比看出,后者是前者时间性能100倍。


1620

我们再看一个例子是关于避免内存copy的问题,#假设我们有许多彼此不相关的向量,但因为一些其他的原因,我们希望将每个向量的第四个元素设为12。

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]][4] <- 12)
#输出
user  system elapsed 
0.051   0.011   0.061 
# 把这些向量一起放到矩阵中
z <- matrix(sample(1:10, m * n, replace = T),nrow = m)
system.time(z[,4] <- 12)
#输出
user  system elapsed 
0.014   0.010   0.023


3.删除临时对象和不再用的对象

  • rm()删除对象 rm(object)删除指定对象,rm(list = ls())可以删除内存中的所有对象

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

4.经常使用分析内存的函数

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

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

5.学会使用并行计算和分布式计算接口

并行计算后端包有如下:

  • doMPI与Rmpi包配合使用

  • doRedis与rredis包配合使用

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

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

下面介绍一下CUDA和R如何搞事情,呵呵。

1620

本来想写一下R+GPU、R+CPP、R+MPI,时间有限以后再向读者介绍。

gc和rm区别

gc不会删除你仍在使用的任何变量,它只释放不再有权访问的内存,运行gc()永远不会让你失去变量。—这很重要


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。