前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >R语言性能Tips和GC

R语言性能Tips和GC

作者头像
吕海峰
发布2018-08-08 10:38:28
1.6K0
发布2018-08-08 10:38:28
举报
文章被收录于专栏:BrianBrian

概述

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

GC

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

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

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

R GC()
R GC()

值得关注的是: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,例如:

代码语言:javascript
复制
 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
代码语言:javascript
复制
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等函数对矩阵或数据库做整体处理)。如果我们在函数定义时加了逻辑判断表达式会破坏向量化计算的。

代码语言:javascript
复制
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转化向量化运算。

代码语言:javascript
复制
# 利用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,预先给对象分配内存。

代码语言:javascript
复制
## 求出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]
    }
)

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

R Bind
R Bind

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

代码语言:javascript
复制
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如何搞事情,呵呵。

R + GPU stack
R + GPU stack

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

gc和rm区别

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

算法总结

R是一门解释性动态语言,知道这一点对于你在采用计算机科学+统计学思想编程时会得心应手。根据你的业务问题,采用统计学算法包、面向函数编程或者面向对象编程都是很简单,因为R包装了很多统计学包,无须关注底层思想和实现,可以说是开箱即用。但是想站在数据科学和算法层面分析问题时,必须深入理解算法底层和设计思想,这样你才能事半功倍。一个优秀的算法专家或数据科学家,首先是一名合格的工程师。算法层面一定对高级抽样技术、高级贝叶斯分析、统计学习方法、现代分层分位回归、复杂数据的统计推断以及统计预测区间(双参数指数分布预测区间、韦布尔分布与极值分布预测区间、正太分布预测区间和幂律过程的预测区间)等知识深入理解,并不断实践和应用理论算法解决问题,才会让自己的内功不断强大。 路漫漫其修远兮,吾将上下而求索

参考文献

1.R语言垃圾回收机制 2.R memory 3.ParallelR

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • GC
  • 如何做
  • 实践
  • gc和rm区别
  • 算法总结
  • 参考文献
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档