人们用什么技巧来管理交互式R会话的可用内存?我使用下面的函数[根据Petr Pikal和David Hinds在2004年的r-help列表中的帖子]列出(和/或排序)最大的对象,偶尔列出rm()
其中的一些.但到目前为止,最有效的解决方案是在具有充足内存的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) }
hadley.. 187
确保以可重现的脚本记录您的工作.有时,重新打开R,然后重新打开source()
脚本.你将清除你不再使用的任何东西,并且作为额外的好处将测试你的代码.
确保以可重现的脚本记录您的工作.有时,重新打开R,然后重新打开source()
脚本.你将清除你不再使用的任何东西,并且作为额外的好处将测试你的代码.
我使用data.table包.通过其:=
操作员,您可以:
按引用添加列
通过引用和按引用分组修改现有列的子集
按引用删除列
这些操作data.table
都不会复制(可能很大),甚至不复制一次.
聚合也特别快,因为data.table
使用更少的工作内存.
相关链接 :
来自data.table的新闻,伦敦R演讲,2012年
我什么时候应该:=
在data.table中使用运算符?
在Twitter帖子上看到这个并认为这是Dirk的一个很棒的功能!根据JD Long的回答,我会这样做以方便用户阅读:
# 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) { 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", "Length/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 Length/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) { print(object.size(x), units = "auto") })
我喜欢Dirk的.ls.objects()脚本,但我一直眯着眼睛来计算size列中的字符.所以我做了一些丑陋的黑客,让它出现相当大小的格式:
.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 }
subset
在将数据帧传递给data=
回归函数的参数时,我主动使用参数并仅选择所需的变量.如果我忘记在公式和select=
向量中添加变量,它确实会导致一些错误,但由于减少了对象的复制并显着减少了内存占用,它仍然节省了大量时间.假设我拥有包含110个变量的400万条记录(我也是.)示例:
# 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") ) )
通过设置上下文和策略:gdlab2
变量是为数据集中的主体构建的逻辑向量,该数据集具有所有正常或几乎正常的一组实验室测试值,并且HIVfinal
是一个特征向量,总结了HIV的初步和确认测试.
这是一个很好的技巧.
另一个建议是尽可能使用内存有效的对象:例如,使用矩阵而不是data.frame.
这并没有真正解决内存管理问题,但一个不为人所知的重要功能是memory.limit().您可以使用此命令memory.limit(size = 2500)增加默认值,其中大小以MB为单位.正如Dirk所提到的,你需要使用64位才能真正利用这一点.
我非常喜欢Dirk开发的改进对象功能.但很多时候,对象名称和大小的基本输出对我来说已经足够了.这是一个具有类似目标的简单函数.内存使用可以按字母顺序或按大小排序,可以限制为一定数量的对象,也可以按升序或降序排序.此外,我经常处理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
我从不保存R工作区.我使用导入脚本和数据脚本,并输出任何我不想经常复制到文件的特别大的数据对象.这样我总是从一个新的工作区开始,不需要清理大的物体.这是一个非常好的功能.
不幸的是,我没有时间对它进行广泛的测试,但这是一个我以前从未见过的记忆提示.对我来说,所需的内存减少了50%以上.当您使用例如read.csv将内容读入R时,它们需要一定量的内存.在此之后你可以保存它们save("Destinationfile",list=ls())
.下次打开R时你可以使用load("Destinationfile")
现在内存使用量可能会减少.如果有人能够确认这是否会产生与不同数据集类似的结果,那将是很好的.
为了进一步说明频繁重启的常见策略,我们可以使用littler,它允许我们直接从命令行运行简单表达式.这是一个我有时用来为一个简单的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总是开始"新鲜",这种方法在包开发过程中也是一个很好的测试.
最后但并非最不重要的是,对于使用'#!/ usr/bin/r'shebang-header的脚本中的自动批处理模式,r也很有效.Rscript是一个替代品,其中littler不可用(例如在Windows上).
出于速度和内存的目的,当通过一系列复杂的步骤构建大型数据帧时,我会定期将它(正在构建的正在进行的数据集)刷新到磁盘,附加到之前的任何内容,然后重新启动它.这样,中间步骤仅适用于较小的数据帧(这是好的,例如,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而不是例如矩阵,数组,列表.
我很幸运,我的大数据集由仪器保存在大约100 MB(32位二进制)的"块"(子集)中.因此,我可以在融合数据集之前依次执行预处理步骤(删除无信息部分,下采样).
gc ()
如果数据大小接近可用内存,则"手动" 调用可能会有所帮助.
有时,不同的算法需要更少的内存.
有时在矢量化和内存使用之间存在折衷.
比较:split
&lapply
与for
循环.
为了快速简便地进行数据分析,我经常首先使用数据的一个小的随机子集(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.matrix
或data.table
允许就地改变其内容的结构,可以实现非常有效的存储器使用.
包中的ll
函数gData
也可以显示每个对象的内存使用情况.
gdata::ll(unit='MB')
如果您真的想避免泄漏,则应避免在全局环境中创建任何大对象.
我通常做的是拥有一个完成工作并返回的函数NULL
- 所有数据都在这个函数或它调用的其他函数中被读取和操作.
只有4GB的RAM(运行Windows 10,所以实际上大约2或更多1GB)我必须非常小心分配.
我几乎只使用data.table.
'fread'功能允许您在导入时按字段名称对信息进行子集化; 仅导入实际需要的字段.如果您正在使用基本R读取,请在导入后立即使虚假列为空.
正如42-建议的那样,在可能的情况下,我将在导入信息后立即在列中进行子集化.
我经常在不再需要时从环境中获取rm()对象,例如,在使用它们对其他内容进行子集之后的下一行,并调用gc().
与基本R读写相比,data.table中的'fread'和'fwrite'可以非常快.
正如kpierce8所暗示的那样,我几乎总是将一切都从环境中解放出来并将其重新传入,即使有成千上万的小文件也可以通过.这不仅可以保持环境"干净"并保持较低的内存分配,而且可能由于严重缺乏可用内存,R可能会频繁崩溃在我的计算机上; 真的经常.随着代码在各个阶段的进展,将信息备份到驱动器本身意味着如果崩溃,我不必从头开始.
截至2017年,我认为最快的SSD通过M2端口每秒运行几GB.我有一个非常基本的50GB金士顿V300(550MB/s)SSD,我用它作为我的主磁盘(上面有Windows和R).我将所有批量信息保存在便宜的500GB WD盘片上.当我开始处理它时,我将数据集移动到SSD.这与'fread'ing'和'fwrite'相结合,一切都很好.我尝试使用'ff',但更喜欢前者.4K读/写速度可能会产生问题; 从SSD到碟片备份25万个1k文件(价值250MB)可能需要数小时.据我所知,目前还没有任何可用的R软件包可以自动优化"chunkification"过程; 例如,查看用户拥有多少RAM,测试RAM /所有连接驱动器的读/写速度,然后建议最佳的"chunkification"协议.这可以产生一些重要的工作流程改进/资源优化; 例如将它拆分为... MB用于RAM - >将其拆分为... MB用于SSD - >将其拆分为... MB放在盘子上 - >将其拆分为... MB在磁带上.它可以预先对数据集进行采样,以便为其提供更实际的标尺.
我在R中遇到的许多问题涉及形成组合和置换对,三元组等,这使得有限的RAM更多地受到限制,因为它们通常至少会在某个时刻呈指数级扩展.这使我集中了大量的注意力放在质量而不是数量的信息进入他们开始,而不是试图事后清理,并在准备资料的操作顺序与(从开始最简单的操作,增加复杂性); 例如子集,然后合并/加入,然后形成组合/排列等.
在某些情况下,使用基本R读写似乎有一些好处.例如,'fread'中的错误检测非常好,可能很难尝试将真正混乱的信息写入R以开始清理它.如果您使用Linux,Base R似乎也更容易.Base R似乎在Linux中运行良好,Windows 10使用~20GB的磁盘空间而Ubuntu只需要几GB,Ubuntu所需的RAM略低.但是在(L)Ubuntu中安装第三方软件包时,我注意到了大量的警告和错误.我不建议离开(L)Ubuntu或Linux上的其他股票分发太远,因为你可以放松这么多的整体兼容性,这使得这个过程几乎毫无意义(我认为'团结'将于2017年在Ubuntu取消).我意识到这对于一些Linux用户来说不会很好,但是一些自定义发行版的界限毫无意义(我花了数年时间单独使用Linux).
希望其中一些可能会帮助其他人.
这没有增加上述内容,但是用我喜欢的简单且评论很多的风格编写.它产生一个表格,其中的对象按大小排序,但没有上面示例中给出的一些细节:
#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]))),])