首页 文章

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

提问于
浏览
456

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

25 回答

  • 147

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

    my.matrix <- Matrix(my.matrix)
    

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

    另外,我收到的原始文件是'long'格式,其中每个数据点都有变量 x, y, z, i . 将数据转换为仅具有变量 ix * y * z 维数组的效率更高 .

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

  • 17

    为了进一步说明频繁重启的常见策略,我们可以使用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总是开始“新鲜”,这种方法在包开发过程中也是一个很好的测试 .

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

  • 2

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

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

  • 25

    运行

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

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

  • 33

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

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

    如果您正在使用 Linux 并且想要使用 several processes 并且只需要对一个或多个 large objects 执行 read 操作 makeForkCluster 而不是 makePSOCKcluster . 这也节省了将大对象发送到其他进程的时间 .

  • 47

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

    • 按引用添加列

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

    • 按引用删除列

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

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

    相关链接 :

  • 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 等结构相结合,允许就地更改其内容,可以非常有效地使用内存实现 .

  • 100

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

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

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

    例:

    part1
    ```{r corpus, warning=FALSE, cache=TRUE, message=FALSE, eval=TRUE}
    corpusTw <- corpus(twitter)  # build the corpus
    

    part2

    dfmTw <- dfm(corpusTw, verbose=TRUE, removeTwitter=TRUE, ngrams=3)
    
    
    作为副作用,这也可以在再现性方面为您节省一些麻烦:)
  • 2

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

  • 48

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

  • 1

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

    我真的很感激上面的一些答案,在@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并释放内存 .

    希望能帮助到你 .

  • 1

    对于这个优秀的老问题,这是一个较新的答案 . 来自哈德利的高级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

  • 1

    基于@Dirk 's and @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()
    
  • 6

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

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

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

  • 0

    只有4GB的RAM(运行Windows 10,所以大约2或更多,实际上是1GB)我必须非常小心分配 .

    我几乎只使用data.table .

    'fread'功能允许您在导入时按字段名称对信息进行子集化;仅导入实际需要的字段 . 如果您正在使用基本R读取,请在导入后立即将伪列清空 .

    正如42-建议的那样,在可能的情况下,我将在导入信息后立即在列中进行子集化 .

    一旦不再需要,我经常从环境中获取rm()对象,例如在使用它们对其他内容进行子集之后的下一行,并调用gc() .

    与基本R读写相比,data.table中的'fread'和'fwrite'可以非常快 .

    正如kpierce8所暗示的那样,我几乎总是将所有东西都写出环境并将其重新传入,即使有成千上万的小文件也可以通过 . 这不仅可以保持环境'clean'并保持较低的内存分配,但可能由于严重缺乏可用RAM,R可能会频繁崩溃在我的计算机上;真的经常 . 随着代码在各个阶段的进展,将信息备份到驱动器本身意味着如果它崩溃,我不必从头开始 .

    截至2017年,我认为最快的SSD通过M2端口每秒运行几GB . 我有一个非常基本的50GB金士顿V300(550MB / s)SSD,我用它作为我的主磁盘(上面有Windows和R) . 我将所有批量信息保存在便宜的500GB WD盘片上 . 当我开始处理它时,我将数据集移动到SSD . 这个,加上'fread'ing'和'fwrite',一切都很好 . 我尝试使用'ff',但更喜欢前者 . 4K读/写速度可能会产生问题;从SSD到碟片备份25万个1k文件( Value 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) .

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

  • 3

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

  • 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
    
  • 14
    • 我很幸运,我的大数据集由仪器以大约100 MB(32位二进制)的“块”(子集)保存 . 因此,我可以在融合数据集之前按顺序执行预处理步骤(删除无信息部分,下采样) .

    • 如果数据大小接近可用内存,则调用 gc () "by hand"会有所帮助 .

    • 有时不同的算法需要更少的内存 .
      有时在矢量化和内存使用之间存在折衷 .
      比较: splitlapplyfor 循环 .

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

  • 28

    这是一个很好的技巧 .

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

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

  • 30

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

    gdata::ll(unit='MB')
    
  • 7

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

相关问题