提问



人们用什么技巧来管理交互式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)
}

最佳参考


确保以可重现的脚本记录您的工作。不时地,重新打开R,然后source()你的剧本。你将清除你不再使用的任何东西,并且作为额外的好处将测试你的代码。

其它参考1


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



  • 按引用添加列

  • 按引用修改现有列的子集,按引用分组修改

  • 按引用删除列



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



  • 聚合也特别快,因为data.table使用的工作内存要少得多。



相关链接 :



  • 来自data.table的新闻,伦敦R演示文稿,2012

  • 我应该何时在data.table中使用:=运算符?


其它参考2


在Twitter帖子上看到这个并认为这是Dirk的一个很棒的功能!继JD Long的回答之后,我会这样做以方便用户阅读:[58]


# 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") })

其它参考3


我喜欢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
}

其它参考4


当将数据帧传递给回归函数的data=参数时,我积极使用subset参数,只选择所需的变量。如果我忘记在公式和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的初步和确认测试。

其它参考5


这是一个很好的伎俩。


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


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

其它参考6


我非常喜欢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

其它参考7


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

其它参考8


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

其它参考9


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


 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总是开始新鲜,这种方法在包开发期间也是一个很好的测试。


最后但并非最不重要的是,r对于使用#!/usr/bin/rshebang-header的脚本中的自动批处理模式也很有用。 Rscript是一个替代品,其中littler不可用(例如在Windows上)。

其它参考10


对于速度和内存目的,当通过一系列复杂的步骤构建大型数据帧时,我会定期将它(正在构建的正在进行的数据集)刷新到磁盘,附加到之前的任何内容,然后重新启动它这样,中间步骤只适用于较小的数据帧(这很好,例如, 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 )

其它参考11


请注意,data.table包[[s tables()似乎是Dirk .ls.objects()自定义函数(在前面的答案中有详细介绍)的一个很好的替代品,尽管只是对于data.frames/tables而不是例如矩阵,数组,列表。

其它参考12



  1. 我很幸运,我的大数据集由仪器以大约100 MB(32位二进制)的块(子集)保存。因此,我可以按顺序执行预处理步骤(删除无信息部分,下采样)融合数据集。

  2. 如果数据大小接近可用内存,则手动调用gc ()会有所帮助。

  3. 有时候不同的算法需要更少的内存
    有时在矢量化和记忆使用之间存在折衷
    比较:split& lapplyfor循环相比。

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


其它参考13


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


原因是:每次修改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之类的结构允许就地改变它们的内容,可以实现非常有效的存储器使用。

其它参考14


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


gdata::ll(unit='MB')

其它参考15


只有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和fwrite结合起来一直很好。我曾尝试使用ff但更喜欢前者。虽然4K读/写速度可能会产生问题;从SSD到盘片备份25万个1k文件(250MB)可能需要数小时。正如我所知,目前还没有任何可用的R软件包可以自动优化分块化过程;例如,查看用户拥有多少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)。


希望其中一些可能会帮助其他人。

其它参考16


如果您真的想避免泄漏,则应避免在全局环境中创建任何大对象。


我通常做的是拥有一个完成工作并返回NULL的函数 - 所有数据都在这个函数或它调用的其他函数中被读取和操作。

其它参考17


这没有增加上述内容,但是用我喜欢的简单且评论很多的风格编写。它产生一个表,其中的对象按大小排序,但没有上面示例中给出的一些细节:


#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]))),])

其它参考18


如果您正在使用 Linux 并希望使用多个流程,只需对一个或多个大型对象执行读取操作/strong>使用makeForkCluster而不是makePSOCKcluster。这也节省了将大对象发送到其他进程的时间。

其它参考19


我真的很感激上面的一些答案,在@hadley和@Dirk之后建议关闭R并发出source并使用命令行我想出了一个对我来说非常好的解决方案。我不得不处理数百个质谱,每个占用大约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并释放内存。


希望能帮助到你。

其它参考20


除了上面的答案中给出的更一般的内存管理技术,我总是尽量减少对象的大小。例如,我使用非常大但非常稀疏的矩阵,换句话说,大多数值为零的矩阵。使用矩阵包(大写重要)我能够将平均对象大小从~2GB减少到~200MB,简单如下:


my.matrix <- Matrix(my.matrix)


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


另外,我收到的原始文件是长格式,其中每个数据点都有变量x, y, z, i。将数据转换为只有变量ix * y * z维数组更有效。


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

其它参考21


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


我通常将代码分成不同的块,并选择将哪一个保存到缓存或RDS文件,以及


在那里你可以设置一个块保存到缓存,或者你可以决定是否运行一个特定的块。这样,在第一次运行中,您只能处理第1部分,另一次执行只能选择第2部分,等等。


例:


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)
```


作为副作用,这也可以在再现性方面为您节省一些麻烦:)

其它参考22


根据@Dirk's和@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()

其它参考23


对于这个优秀的老问题,这是一个较新的答案。来自哈德利的高级R:


install.packages("pryr")

library(pryr)

object_size(1:10)
## 88 B

object_size(mean)
## 832 B

object_size(mtcars)
## 6.74 kB


(http://adv-r.had.co.nz/memory.html)[61]

其它参考24


运行


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


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