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

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

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

这是个好把戏。

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

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

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

为了进一步说明频繁重启的常见策略,我们可以使用的小,它允许我们直接从命令行运行简单的表达式。这里有一个例子,我有时会用不同的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。Rscript是little不可用的替代方案(例如在Windows上)。

我喜欢Dirk的.ls.objects()脚本,但我总是眯着眼睛数大小列中的字符。所以我做了一些丑陋的hack,使它呈现出漂亮的格式大小:

.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=向量添加变量,它确实会导致一些错误,但由于减少了对象的复制,它仍然节省了大量时间,并显著减少了内存占用。假设我有400万条记录和110个变量(我确实有)。例子:

# 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是一个字符向量,总结了艾滋病毒的初步和确认性测试。

我在推特上看到了这个,觉得德克的功能太棒了!从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") })

出于速度和内存的考虑,当通过一系列复杂的步骤构建一个大型数据帧时,我会定期将它(正在构建的数据集)刷新到磁盘,附加到之前的任何数据集,然后重新启动它。这样,中间步骤只在较小的数据帧上工作(这很好,例如,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 )
不幸的是,我没有时间对它进行广泛的测试,但这里有一个我以前没有见过的记忆技巧。对我来说,所需的内存减少了50%以上。 当你用read。csv把东西读入R时,它们需要一定的内存。 之后你可以用save("Destinationfile",list=ls())保存它们 下次你打开R时,你可以使用load("Destinationfile") 现在内存使用可能已经减少了。 如果有人能确认这是否会在不同的数据集上产生类似的结果,那就太好了

我非常喜欢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
  1. 我很幸运,我的大数据集被仪器保存在大约100 MB(32位二进制)的“块”(子集)中。因此,我可以在融合数据集之前依次执行预处理步骤(删除无信息的部分,降低采样)。

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

  3. 有时候不同的算法需要更少的内存 有时在向量化和内存使用之间需要权衡 对比:split &

  4. . lapplyfor循环
  5. 为了快速&简单的数据分析,我通常先处理一个小的随机子集(sample ())的数据。一旦数据分析脚本/。Rnw是完成的数据分析代码和完整的数据去计算服务器过夜/周末/…计算。

我使用data.table包。使用它的:=操作符,你可以:

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

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

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

相关链接:

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

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

gdata::ll(unit='MB')

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

原因是:每当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这样的结构(允许就地更改其内容),可以实现非常有效的内存使用。

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

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

我真的很欣赏上面的一些答案,遵循@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并释放内存。

希望能有所帮助。

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

my.matrix <- Matrix(my.matrix)

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

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

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

这并没有增加上面的内容,而是以我喜欢的简单和大量注释的风格编写的。它生成一个对象大小排序表,但没有上面例子中给出的一些细节:

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

运行

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

还可以帮助R释放未使用但仍未释放的内存。

使用knitr和将脚本放在Rmd块中也可以获得一些好处。

我通常将代码划分为不同的块,并选择将检查点保存到缓存或RDS文件中

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

例子:

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

作为一个副作用,这也可以让你在可重复性方面省去一些麻烦:)

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

我使用数据。几乎只有桌子。

'fread'函数允许您在导入时按字段名划分信息子集;只导入开始时实际需要的字段。如果使用base R read,则在导入后立即将伪列空。

正如42 -所建议的,只要有可能,我将在导入信息后立即在列中进行子集。

我经常从环境中rm()对象,一旦他们不再需要,例如在使用他们子集其他东西后的下一行,并调用gc()。

'fread'和'fwrite'从数据。表可以非常快与基R读写比较。

正如kpierce8所建议的,我几乎总是把所有东西都从环境中删除,然后再重新写入,即使有成千上万的小文件要通过。这不仅保持环境“干净”,保持内存分配低,但可能是由于严重缺乏可用的RAM, R在我的计算机上有频繁崩溃的倾向;很频繁。随着代码在各个阶段的进展,将信息备份到驱动器本身意味着如果它崩溃了,我不必从头开始。

截至2017年,我认为最快的ssd硬盘通过M2端口运行大约每秒几GB。我有一个非常基本的50GB Kingston V300 (550MB/s) SSD,我把它用作我的主磁盘(上面有Windows和R)。我把所有的大量信息保存在一个便宜的500GB WD盘片上。当我开始处理数据集时,我将数据集移动到SSD。这与“阅读”和“写”相结合,效果非常好。我试过用“ff”,但更喜欢前者。但4K的读写速度会带来问题;将25万个1k文件(250mb)从SSD备份到磁盘可能需要数小时。据我所知,目前还没有任何R包可以自动优化“块化”过程;例如,看看用户有多少内存,测试RAM /所有连接的驱动器的读写速度,然后建议一个最佳的“块化”协议。这可能会产生一些重大的工作流程改进/资源优化;例如,把它分成……MB内存->分割它到…SSD的MB ->分割到…MB在盘片上->分割它到…MB在磁带上。它可以事先对数据集进行采样,以获得更现实的工作标尺。

我在R中处理的很多问题都涉及到组合和排列对,三元组等,这只会使有限的RAM成为一个限制,因为它们经常会在某个点上以指数方式扩展。这让我把大量的注意力集中在质量而不是数量的信息上,而不是在开始时试图清理它,以及在准备信息时的操作顺序(从最简单的操作开始,增加复杂性);例如,子集,然后合并/连接,然后形成组合/排列等。

在某些情况下,使用base R读写似乎确实有一些好处。例如,“fread”中的错误检测非常好,以至于很难将非常混乱的信息输入到R中进行清理。如果您使用的是Linux, Base R似乎也容易得多。Base R在Linux中工作得很好,Windows 10使用大约20GB的磁盘空间,而Ubuntu只需要几GB, Ubuntu所需的RAM略低。但是我注意到在(L)Ubuntu中安装第三方软件包时会出现大量的警告和错误。我不建议远离(L)Ubuntu或其他Linux发行版,因为你会失去太多的整体兼容性,这使得这个过程几乎毫无意义(我认为“unity”将在2017年在Ubuntu中被取消)。我知道一些Linux用户不会接受,但一些自定义发行版除了新奇之外几乎毫无意义(我已经单独使用Linux很多年了)。

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

基于@德克和@托尼的回答,我做了一个小小的更新。结果是在漂亮的大小值之前输出[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()

如果你正在使用Linux,想要使用几个流程,并且只需要在一个或多个大对象上执行操作,请使用makeForkCluster而不是makePSOCKcluster。这也节省了将大对象发送给其他进程的时间。

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

install.packages("pryr")


library(pryr)


object_size(1:10)
## 88 B


object_size(mean)
## 832 B


object_size(mtcars)
## 6.74 kB

(# EYZ0)

当使用需要大量繁重计算和中间步骤来创建的对象时,我经常发现写一段代码块来创建对象,然后写一段单独的代码块,让我可以选择生成对象并将其保存为rmd文件,或者从我之前保存的rmd文件中从外部加载它。在R Markdown中使用以下代码块结构特别容易做到这一点。

```{r Create OBJECT}


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


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


LOAD <- TRUE
SAVE <- TRUE
#NOTE: Set LOAD to TRUE if you want to load saved file
#NOTE: Set LOAD to FALSE if you want to generate the object from scratch
#NOTE: Set SAVE to TRUE if you want to save the object externally


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


```

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

当我在一个有很多中间步骤的大型项目中工作时,我会尽量减少对象的数量。而不是创建许多唯一的对象

# eyz0 -> # eyz1 -> # eyz2 -> # eyz3 -> # eyz4

# eyz0 -> # eyz1 -> # eyz2 -> # eyz3 -> # eyz4

我使用临时对象,我称之为temp

# eyz0 -> # eyz1 -> # eyz1 -> # eyz1 -> # eyz4

这样就少了一些中间文件,多了一些概览。

raster  <- raster('file.tif')
temp <- raster * 10
temp <- mean(temp)
resultRast <- sqrt(temp)

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

rm(temp)

如果我需要几个中间文件,我使用temp1temp2temp3

对于测试,我使用testtest2,…

rm(list=ls())是一种让你保持诚实和保持内容可重复性的好方法。