技巧来pipe理R会话中的可用内存

人们使用什么技巧来pipe理交互式R会话的可用内存? 我使用下面的函数[根据Petr Pikal和David Hinds在2004年的r-help列表中发布]来列出(和/或sorting)最大的对象,偶尔使用rm()一些。 但是到目前为止,最有效的解决scheme是在64位Linux下运行,内存充足。

任何其他好的技巧人们想分享? 请发邮件。

 # improved list of objects .ls.objects <- function (pos = 1, pattern, order.by, decreasing=FALSE, head=FALSE, n=5) { napply <- function(names, fn) sapply(names, function(x) fn(get(x, pos = pos))) names <- ls(pos = pos, pattern = pattern) obj.class <- napply(names, function(x) as.character(class(x))[1]) obj.mode <- napply(names, mode) obj.type <- ifelse(is.na(obj.class), obj.mode, obj.class) obj.size <- napply(names, object.size) obj.dim <- t(napply(names, function(x) as.numeric(dim(x))[1:2])) vec <- is.na(obj.dim)[, 1] & (obj.type != "function") obj.dim[vec, 1] <- napply(names, length)[vec] out <- data.frame(obj.type, obj.size, obj.dim) names(out) <- c("Type", "Size", "Rows", "Columns") if (!missing(order.by)) out <- out[order(out[[order.by]], decreasing=decreasing), ] if (head) out <- head(out, n) out } # shorthand lsos <- function(..., n=10) { .ls.objects(..., order.by="Size", decreasing=TRUE, head=TRUE, n=n) } 

确保您以可重复的脚本logging您的工作。 不时地重新打开R,然后inputsource()你的脚本。 你将清理掉不再使用的任何东西,并且作为附加的好处将会testing你的代码。

我使用data.table包。 使用:=运算符,您可以:

  • 通过引用添加列
  • 通过引用修改现有列的子集,并按引用分组
  • 通过引用删除列

这些操作都没有复制(可能很大) data.table ,甚至没有一次。

  • 聚合也特别快,因为data.table使用更less的工作内存。

相关链接 :

  • 来自data.table的新闻,伦敦R介绍,2012
  • 什么时候应该在data.table中使用:=运算符?

看到这个在Twitter上的post,认为这是一个令人敬畏的function德克! 从JD龙的回答开始,我会这样做,以便用户友好阅读:

 # improved list of objects .ls.objects <- function (pos = 1, pattern, order.by, decreasing=FALSE, head=FALSE, n=5) { napply <- function(names, fn) sapply(names, function(x) fn(get(x, pos = pos))) names <- ls(pos = pos, pattern = pattern) obj.class <- napply(names, function(x) as.character(class(x))[1]) obj.mode <- napply(names, mode) obj.type <- ifelse(is.na(obj.class), obj.mode, obj.class) obj.prettysize <- napply(names, function(x) { capture.output(format(utils::object.size(x), units = "auto")) }) obj.size <- napply(names, object.size) obj.dim <- t(napply(names, function(x) as.numeric(dim(x))[1:2])) vec <- is.na(obj.dim)[, 1] & (obj.type != "function") obj.dim[vec, 1] <- napply(names, length)[vec] out <- data.frame(obj.type, obj.size, obj.prettysize, obj.dim) names(out) <- c("Type", "Size", "PrettySize", "Rows", "Columns") if (!missing(order.by)) out <- out[order(out[[order.by]], decreasing=decreasing), ] if (head) out <- head(out, n) out } # shorthand lsos <- function(..., n=10) { .ls.objects(..., order.by="Size", decreasing=TRUE, head=TRUE, n=n) } lsos() 

其结果如下所示:

  Type Size PrettySize Rows Columns pca.res PCA 790128 771.6 Kb 7 NA DF data.frame 271040 264.7 Kb 669 50 factor.AgeGender factanal 12888 12.6 Kb 12 NA dates data.frame 9016 8.8 Kb 669 2 sd. numeric 3808 3.7 Kb 51 NA napply function 2256 2.2 Kb NA NA lsos function 1944 1.9 Kb NA NA load loadings 1768 1.7 Kb 12 2 ind.sup integer 448 448 bytes 102 NA x character 96 96 bytes 1 NA 

注:我添加的主要部分是(再次,从JD的答案改编):

 obj.prettysize <- napply(names, function(x) { capture.output(print(object.size(x), units = "auto")) }) 

我想不出任何其他方式来从打印输出(…),所以使用capture.output(),我敢肯定是非常低效:)

我喜欢Dirk的.ls.objects()脚本,但我一直眯着眼睛看大小栏中的字符。 所以我做了一些丑陋的黑客,使其呈现出相当大的格式:

 .ls.objects <- function (pos = 1, pattern, order.by, decreasing=FALSE, head=FALSE, n=5) { napply <- function(names, fn) sapply(names, function(x) fn(get(x, pos = pos))) names <- ls(pos = pos, pattern = pattern) obj.class <- napply(names, function(x) as.character(class(x))[1]) obj.mode <- napply(names, mode) obj.type <- ifelse(is.na(obj.class), obj.mode, obj.class) obj.size <- napply(names, object.size) obj.prettysize <- sapply(obj.size, function(r) prettyNum(r, big.mark = ",") ) obj.dim <- t(napply(names, function(x) as.numeric(dim(x))[1:2])) vec <- is.na(obj.dim)[, 1] & (obj.type != "function") obj.dim[vec, 1] <- napply(names, length)[vec] out <- data.frame(obj.type, obj.size,obj.prettysize, obj.dim) names(out) <- c("Type", "Size", "PrettySize", "Rows", "Columns") if (!missing(order.by)) out <- out[order(out[[order.by]], decreasing=decreasing), ] out <- out[c("Type", "PrettySize", "Rows", "Columns")] names(out) <- c("Type", "Size", "Rows", "Columns") if (head) out <- head(out, n) out } 

在将数据框传递给回归函数的data=参数时,我会主动使用subset参数,只select所需的variables。 如果我忘记向公式和select=向量中添加variables,会导致一些错误,但由于减less了对象的复制并显着减less了内存占用,因此仍然节省了大量时间。 假设我有400万条logging和110个variables(我也是这样)例如:

 # library(rms); library(Hmisc) for the cph,and rcs functions Mayo.PrCr.rbc.mdl <- cph(formula = Surv(surv.yr, death) ~ age + Sex + nsmkr + rcs(Mayo, 4) + rcs(PrCr.rat, 3) + rbc.cat * Sex, data = subset(set1HLI, gdlab2 & HIVfinal == "Negative", select = c("surv.yr", "death", "PrCr.rat", "Mayo", "age", "Sex", "nsmkr", "rbc.cat") ) ) 

通过设置上下文和策略: gdlab2variables是一个逻辑向量,它是为一组数据集中的受试者构build的,该variables对于一系列实验室testing具有所有正常或接近正常的值,而HIVfinal是一个概括初步和validation性testing为HIV。

不幸的是,我没有时间对它进行广泛的testing,但是这是一个我以前从未见过的记忆提示。 对我来说,所需的内存减less了50%以上。 当你读入R的东西与例如read.csv他们需要一定量的内存。 在此之后,您可以使用save("Destinationfile",list=ls())保存它们。下次打开R时,可以使用load("Destinationfile")现在内存使用量可能已经减less。 如果有人可以确认这是否会产生与其他数据集相似的结果,那将会很好。

我非常喜欢Dirk开发的改进的对象function。 很多时候,对象名称和大小的更基本的输出对我来说已经足够了。 这是一个简单的function,具有类似的目标。 内存使用可以按字母顺序或按大小sorting,可以限制到一定数量的对象,并可以按顺序升序或降序。 另外,我经常使用1GB +的数据,所以函数会相应地改变单位。

 showMemoryUse <- function(sort="size", decreasing=FALSE, limit) { objectList <- ls(parent.frame()) oneKB <- 1024 oneMB <- 1048576 oneGB <- 1073741824 memoryUse <- sapply(objectList, function(x) as.numeric(object.size(eval(parse(text=x))))) memListing <- sapply(memoryUse, function(size) { if (size >= oneGB) return(paste(round(size/oneGB,2), "GB")) else if (size >= oneMB) return(paste(round(size/oneMB,2), "MB")) else if (size >= oneKB) return(paste(round(size/oneKB,2), "kB")) else return(paste(size, "bytes")) }) memListing <- data.frame(objectName=names(memListing),memorySize=memListing,row.names=NULL) if (sort=="alphabetical") memListing <- memListing[order(memListing$objectName,decreasing=decreasing),] else memListing <- memListing[order(memoryUse,decreasing=decreasing),] #will run if sort not specified or "size" if(!missing(limit)) memListing <- memListing[1:limit,] print(memListing, row.names=FALSE) return(invisible(memListing)) } 

这里有一些输出示例:

 > showMemoryUse(decreasing=TRUE, limit=5) objectName memorySize coherData 713.75 MB spec.pgram_mine 149.63 kB stoch.reg 145.88 kB describeBy 82.5 kB lmBandpass 68.41 kB 

这是一个很好的窍门。

另一个build议是尽可能使用有效率的内存对象:例如,使用一个matrix而不是data.frame。

这并没有真正解决内存pipe理问题,但是一个并不广为人知的重要function是memory.limit()。 您可以使用此命令增加默认值,memory.limit(size = 2500),其中大小以MB为单位。 正如Dirk所说的,你需要使用64位才能真正利用这一点。

我从不保存R工作区。 我使用导入脚本和数据脚本,并输出任何特别大的数据对象,我不想经常重新创build文件。 这样我总是从一个新的工作空间开始,不需要清理大对象。 虽然这是一个非常好的function。

为了进一步说明频繁重启的常用策略,我们可以使用littler ,它允许我们直接从命令行运行简单的expression式。 这里有一个例子,我有时会用一个简单的crossprod来计算不同的BLAS。

  r -e'N<-3*10^3; M<-matrix(rnorm(N*N),ncol=N); print(system.time(crossprod(M)))' 

同样,

  r -lMatrix -e'example(spMatrix)' 

加载Matrix包(通过–packages | -l开关)并运行spMatrix函数的示例。 由于r总是开始“新鲜”,所以这个方法在包开发过程中也是一个很好的testing。

最后但并非最不重要的是,对于使用'#!/ usr / bin / r'shebang-header的脚本来说,自动批处理模式也是非常有效的。 Rscript是一个替代scheme,在小问题是不可用的(例如在Windows上)。

为了速度和内存的目的,当通过一系列复杂的步骤构build大型数据框时,我会定期将其刷新(正在创build的进行中的数据集)到磁盘上,附加到之前的任何内容,然后重新启动。 这样,中间步骤只能在较小的dataframe上工作(这比较好,因为例如rbind会随着较大的对象而显着减慢)。 当所有中间对象都被删除时,整个数据集可以在过程结束时读回。

 dfinal <- NULL first <- TRUE tempfile <- "dfinal_temp.csv" for( i in bigloop ) { if( !i %% 10000 ) { print( i, "; flushing to disk..." ) write.table( dfinal, file=tempfile, append=!first, col.names=first ) first <- FALSE dfinal <- NULL # nuke it } # ... complex operations here that add data to 'dfinal' data frame } print( "Loop done; flushing to disk and re-reading entire data set..." ) write.table( dfinal, file=tempfile, append=TRUE, col.names=FALSE ) dfinal <- read.table( tempfile ) 

只要注意data.table包的tables()似乎是Dirk的.ls.objects()自定义函数的一个很好的替代品(详见前面的答案),虽然对于data.frames / tables而不是matrix,数组,名单。

  1. 我很幸运,我的大型数据集由仪器保存在大约100 MB(32位二进制)的“块”(子集)中。 因此,在融合数据集之前,我可以依次执行预处理步骤(删除无效部分,缩减采样)。

  2. 如果数据的大小接近可用内存,则“手动调用” gc ()可以提供帮助。

  3. 有时候不同的algorithm需要更less的内存。
    有时在vector化和内存使用之间有一个权衡。
    比较: splitlapplyfor循环。

  4. 为了快速简单的数据分析,我经常首先使用数据的一个小的随机子集( sample () )。 一旦数据分析脚本/ .Rnw完成数据分析代码,并将完整的数据转到计算服务器进行过夜/周末/ …计算。

使用环境而不是列表来处理占用大量工作内存的对象的集合。

原因是:每次list结构的元素被修改时,整个列表都被临时复制。 如果列表的存储需求大约是可用工作存储器的一半,则这成为一个问题,因为这时数据必须交换到慢速硬盘。 另一方面,环境不会受到这种行为的影响,它们可以被视为与列表类似。

这里是一个例子:

 get.data <- function(x) { # get some data based on x return(paste("data from",x)) } collect.data <- function(i,x,env) { # get some data data <- get.data(x[[i]]) # store data into environment element.name <- paste("V",i,sep="") env[[element.name]] <- data return(NULL) } better.list <- new.env() filenames <- c("file1","file2","file3") lapply(seq_along(filenames),collect.data,x=filenames,env=better.list) # read/write access print(better.list[["V1"]]) better.list[["V2"]] <- "testdata" # number of list elements length(ls(better.list)) 

结合诸如big.matrixdata.table结构,可以在原地更改其内容,可以实现非常有效的内存使用。

gData包中的ll函数也可以显示每个对象的内存使用情况。

 gdata::ll(unit='MB') 

如果你真的想避免泄漏,你应该避免在全球环境中创build任何大的对象。

我通常做的是有一个函数做这个工作,并返回NULL – 所有的数据在这个函数或它调用的其他函数中读取和操纵。

我真的很感谢上面的一些答案,在@hadley和@Dirk后面提示closuresR并发布source和使用命令行,我想出了一个对我来说工作得很好的解决scheme。 我不得不处理数以百计的质谱,每个都占用大约20 Mb的内存,所以我使用了两个R脚本,如下所示:

首先是一个包装:

 #!/usr/bin/Rscript --vanilla --default-packages=utils for(l in 1:length(fdir)) { for(k in 1:length(fds)) { system(paste("Rscript runConsensus.r", l, k)) } } 

通过这个脚本,我基本上可以控制我的主脚本runConsensus.r ,并为输出写入数据答案。 有了这个,每次包装器调用脚本,似乎R被重新打开,内存被释放。

希望它有帮助。

这并没有增加上述内容,而是写在我喜欢的简单和重要的评论风格。 它会生成一个表格,其中的对象的大小sorting,但没有上述示例中给出的一些细节:

 #Find the objects MemoryObjects = ls() #Create an array MemoryAssessmentTable=array(NA,dim=c(length(MemoryObjects),2)) #Name the columns colnames(MemoryAssessmentTable)=c("object","bytes") #Define the first column as the objects MemoryAssessmentTable[,1]=MemoryObjects #Define a function to determine size MemoryAssessmentFunction=function(x){object.size(get(x))} #Apply the function to the objects MemoryAssessmentTable[,2]=t(t(sapply(MemoryAssessmentTable[,1],MemoryAssessmentFunction))) #Produce a table with the largest objects first noquote(MemoryAssessmentTable[rev(order(as.numeric(MemoryAssessmentTable[,2]))),]) 

除了上面答案中给出的更一般的内存pipe理技术之外,我总是尽可能地减小对象的大小。 例如,我使用非常大但非常稀疏的matrix,换句话说,大多数值为零的matrix。 使用“matrix”包(大写字母重要)我能够将我的平均对象大小从〜2GB减less到〜200MB,如下所示:

 my.matrix <- Matrix(my.matrix) 

Matrix软件包包含的数据格式可以像常规matrix一样使用(无需更改其他代码),但能够更加高效地存储稀疏数据,不pipe是加载到内存还是保存到磁盘。

此外,我收到的原始文件是“长”格式,其中每个数据点都有variablesx, y, z, i 。 将数据转换为只有variablesix * y * z维数组效率更高。

了解你的数据,并使用一些常识。

你也可以使用knitr获得一些好处,并把你的脚本放在Rmd chunck中。

我通常将代码分成不同的块,并select哪一个将检查点保存到caching或RDS文件

在那里你可以设置一个块保存到“caching”,或者你可以决定运行或不是一个特定的块。 这样,第一次运行只能处理“第一部分”,另外一个执行只能select“第二部分”等。

例:

 part1 ```{r corpus, warning=FALSE, cache=TRUE, message=FALSE, eval=TRUE} corpusTw <- corpus(twitter) # build the corpus ``` part2 ```{r trigrams, warning=FALSE, cache=TRUE, message=FALSE, eval=FALSE} dfmTw <- dfm(corpusTw, verbose=TRUE, removeTwitter=TRUE, ngrams=3) ``` 

作为一个副作用,这也可以为您节省一些头痛的重复性:)

运行

 for (i in 1:10) gc(reset = T) 

不时还有助于R释放未使用但仍不释放的内存。

只有4GB的内存(运行Windows 10,所以大约2个或更多的现实1GB)我必须真正小心分配。

我几乎完全使用data.table。

“fread”function允许您通过导入时的字段名称来分类信息; 只导入实际需要的字段。 如果您正在使用基本R读取,那么在导入之后立即清空虚假列。

正如42-build议的那样,在可能的情况下,我将在导入信息后立即在列内进行分组。

我经常从环境中取出rm()对象,例如在将它们用于其他子集之后的下一行,并调用gc()。

data.table中的'fread'和'fwrite'可以非常快地通过与基本R读写的比较。

正如kpierce8所暗示的那样,我几乎总是把所有的东西都写出来 ,然后让它恢复原状 ,即使有成千上万的小文件也能通过。 这不仅保持了环境的“清洁”,并且保持了内存分配的低下,而且可能由于内存严重缺乏,R有计算机频繁崩溃的倾向; 真的很频繁。 随着代码在各个阶段的进展,将信息备份到驱动器本身意味着如果崩溃,我不必从头开始。

截至2017年,我认为最快的SSD通过M2端口每秒运行几GB。 我有一个非常基本的50GB金士顿V300(550MB / s)SSD,我用它作为我的主磁盘(有Windows和R)。 我把所有的大量信息保存在一个便宜的500GB的WD磁盘上。 当我开始处理数据时,我将数据集移到SSD上。 这一点,“fread”和“fwrite”的一切工作已经很好。 我试过使用'ff',但更喜欢前者。 4K的读写速度可能会造成这个问题, 从SSD向盘中备份25万个1k文件(价值250MB)可能需要数小时。 据我所知,目前还没有任何R软件包可以自动优化“块化”过程, 例如查看用户有多lessRAM,testingRAM /所有连接的驱动器的读/写速度,然后提出最佳的“块化”协议。 这可能会产生一些重要的工作stream程改进/资源优化; 例如将其分割为… MB为RAM – >将其分割为… MB为SSD – >将其分割为…在磁盘上的MB – >将其分割为…在磁带上的MB。 它可以预先对数据集进行采样,使其成为一个更加现实的标尺。

我在R中遇到的很多问题都涉及到形成组合和排列对,三元组等等,这只会使RAM的局限性更加有限,因为它们通常至less指数级上扩展。 这使我关注的是质量而不是信息的 ,而不是试图在之后进行清理,以及准备信息的操作顺序(从开始最简单的操作和增加复杂性); 例如子集,然后合并/连接,然后形成组合/排列等。

在某些情况下,使用基本R读取和写入似乎有一些好处。 例如,'fread'中的错误检测非常好,可能很难尝试将R杂乱的信息清理干净。 如果您使用Linux,Base R似乎也更容易。 Base R似乎在Linux中工作正常,Windows 10使用〜20GB的磁盘空间,而Ubuntu只需要几GB,而Ubuntu所需的RAM略低。 但在(L)Ubuntu中安装第三方软件包时,我注意到了大量的警告和错误。 我不会推荐漂移离(L)Ubuntu或其他Linux发行版太远,因为你可能会失去整体兼容性,这使得这个过程几乎毫无意义(我认为Ubuntu将于2017年取消“统一” )。 我意识到这对一些Linux用户来说不会很好,但是一些定制的发行版本是毫无意义的(我花了几年的时间只用了Linux)。

希望有一些可能会帮助别人。

基于@Dirk和@ Tony的回答,我做了一个小小的更新。 结果是在相当大的值之前输出[1] ,所以我拿出了解决问题的capture.output

 .ls.objects <- function (pos = 1, pattern, order.by, decreasing=FALSE, head=FALSE, n=5) { napply <- function(names, fn) sapply(names, function(x) fn(get(x, pos = pos))) names <- ls(pos = pos, pattern = pattern) obj.class <- napply(names, function(x) as.character(class(x))[1]) obj.mode <- napply(names, mode) obj.type <- ifelse(is.na(obj.class), obj.mode, obj.class) obj.prettysize <- napply(names, function(x) { format(utils::object.size(x), units = "auto") }) obj.size <- napply(names, utils::object.size) obj.dim <- t(napply(names, function(x) as.numeric(dim(x))[1:2])) vec <- is.na(obj.dim)[, 1] & (obj.type != "function") obj.dim[vec, 1] <- napply(names, length)[vec] out <- data.frame(obj.type, obj.size, obj.prettysize, obj.dim) names(out) <- c("Type", "Size", "PrettySize", "Rows", "Columns") if (!missing(order.by)) out <- out[order(out[[order.by]], decreasing=decreasing), ] if (head) out <- head(out, n) return(out) } # shorthand lsos <- function(..., n=10) { .ls.objects(..., order.by="Size", decreasing=TRUE, head=TRUE, n=n) } lsos()