诀窍来管理R会话中的可用内存

问题描述 投票:469回答:27

人们用什么技巧来管理交互式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)
}
memory-management r
27个回答
186
投票

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


25
投票

为了进一步说明频繁重启的常见策略,我们可以使用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上)。


23
投票

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

17
投票

需要注意的是,data.table包的tables()似乎是Dirk的.ls.objects()自定义函数(在前面的答案中有详细介绍)的一个相当不错的替代品,虽然仅适用于data.frames / tables而不是例如矩阵,数组,列表。


14
投票
  1. 我很幸运,我的大数据集由仪器以大约100 MB(32位二进制)的“块”(子集)保存。因此,我可以在融合数据集之前依次执行预处理步骤(删除无信息部分,下采样)。
  2. 如果数据大小接近可用内存,则“手动”调用gc ()会有所帮助。
  3. 有时,不同的算法需要更少的内存。 有时在矢量化和内存使用之间存在折衷。 比较:splitlapplyfor循环。
  4. 为了快速简便地进行数据分析,我经常首先使用数据的小随机子集(sample ())。一旦数据分析脚本/ .Rnw完成,数据分析代码和完整数据就会进入计算服务器过夜/周末/ ...计算。

11
投票

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

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


7
投票

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

ll

6
投票

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

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


6
投票

只有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)。

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


5
投票

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

gdata::ll(unit='MB')

4
投票

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

NULL

(Qazxswpoi)


154
投票

我使用data.table包。使用:=操作员,您可以:

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

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

  • 聚合也特别快,因为data.table使用更少的工作记忆。

相关链接 :


3
投票

如果您正在使用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]))),]) 而不是install.packages("pryr") library(pryr) object_size(1:10) ## 88 B object_size(mean) ## 832 B object_size(mtcars) ## 6.74 kB 。这也节省了将大对象发送到其他进程的时间。


2
投票

我非常感谢上面的一些答案,在@hadley和@Dirk建议关闭R并发布http://adv-r.had.co.nz/memory.html并使用命令行后,我想出了一个对我来说非常好的解决方案。我不得不处理数百个质谱,每个占用大约20 Mb的内存,所以我使用了两个R脚本,如下所示:

首先是一个包装器:

makeForkCluster

使用这个脚本我基本上控制我的主脚本做makePSOCKcluster,我写输出的数据答案。有了这个,每次包装器调用脚本时,似乎重新打开R并释放内存。

希望能帮助到你。


2
投票

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

source

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

另外,我收到的原始文件是“长”格式,其中每个数据点都有变量#!/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.rmy.matrix <- Matrix(my.matrix) 维数阵列更有效。

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


2
投票

处理需要大量中间计算的对象的提示:当使用需要大量繁重计算和中间步骤来创建的对象时,我经常发现用函数编写一大块代码来创建对象很有用,然后是一个单独的块代码,它给我选项生成和保存对象作为x, y, z, i文件,或从我之前保存的x * y * z文件外部加载它。使用以下代码块结构在i中这很容易做到。

rmd

使用此代码结构,我需要做的就是根据我是否要生成并保存对象来更改rmd,或者直接从现有的已保存文件加载它。 (当然,我必须生成它并在第一次保存它,但在此之后我可以选择加载它。)设置R Markdown绕过我复杂功能的使用并避免其中所有繁重的计算。此方法仍然需要足够的内存来存储感兴趣的对象,但它使您无需在每次运行代码时进行计算。对于需要大量计算中间步骤的对象(例如,对于涉及大数组上的循环的计算),这可以节省大量的时间和计算。


1
投票

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

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

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

例:

```{r Create OBJECT}

COMPLICATED.FUNCTION <- function(...) { Do heavy calculations needing lots of memory;
                                        Output OBJECT; }

```
```{r Generate or load OBJECT}

LOAD <- TRUE;
#NOTE: Set LOAD to TRUE if you want to load saved file
#NOTE: Set LOAD to FALSE if you want to generate and save

if(LOAD == TRUE) { OBJECT <- readRDS(file = 'MySavedObject.rds'); } else
                 { OBJECT <- COMPLICATED.FUNCTION(x, y, z);
                             saveRDS(file = 'MySavedObject.rds', object = OBJECT); }

```

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


1
投票

基于@Dirk和@Tony的回答我做了一个小小的更新。结果是在漂亮的大小值之前输出LOAD,所以我拿出LOAD = TRUE解决了这个问题:

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

0
投票

运行

[1]

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


0
投票

在使用大量中间步骤的大型项目中工作时,我尝试保持较小的对象数量。因此,而不是创建许多称为的唯一对象

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() - > for (i in 1:10) gc(reset = T) - > dataframe - > step1

step2-> step3 - > result - > raster - > multipliedRast

我使用我称之为meanRastF的临时对象。

sqrtRast - > resultRast - > temp - > dataframe - > temp

这让我有更少的中间文件和更多的概述。

temp

为了节省更多内存,我可以在不再需要时删除temp

result

如果我需要几个中间文件,我使用raster <- raster('file.tif') temp <- raster * 10 temp <- mean(temp) resultRast <- sqrt(temp) temprm(temp)

为了测试我使用temp1temp2,...


106
投票

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

48
投票

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

48
投票

在将数据帧传递给回归函数的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是总结初步和确认测试的字符向量为艾滋病毒。


33
投票

这是一个很好的技巧。

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

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


31
投票

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

30
投票

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


29
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.