Truques para gerenciar a memory disponível em uma session R

Que truques as pessoas usam para gerenciar a memory disponível de uma session R interativa? Eu uso as funções abaixo [baseadas em postagens de Petr Pikal e David Hinds para a lista de ajuda r em 2004] para listar (e / ou ordenar) os maiores objects e ocasionalmente rm() alguns deles. Mas, de longe, a solução mais eficaz foi … rodar em Linux de 64 bits com muita memory.

Quaisquer outros truques legais que as pessoas querem compartilhar? Um por post, por favor.

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

Certifique-se de gravar seu trabalho em um script reproduzível. De vez em quando, reabra R e, em seguida, source() seu script. Você limpará qualquer coisa que não esteja mais usando e, como benefício adicional, terá testado seu código.

Eu uso o pacote data.table . Com o seu operador := você pode:

  • Adicione colunas por referência
  • Modificar subconjuntos de colunas existentes por referência e por grupo por referência
  • Excluir colunas por referência

Nenhuma dessas operações copia os dados (potencialmente grandes) de data.table alguma, nem mesmo uma vez.

  • A agregação também é particularmente rápida porque o data.table usa muito menos memory de trabalho.

Links Relacionados :

  • Notícias de data.table, apresentação de Londres R, 2012
  • Quando devo usar o operador := em data.table?

Vi isso em um post no Twitter e acho que é uma function incrível de Dirk! Na sequência da resposta de JD Long, eu faria isso para leitura amigável:

 # 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() 

Que resulta em algo como o seguinte:

  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 

NOTA: A parte principal que eu adicionei foi (novamente, adaptada da resposta de JD):

 obj.prettysize <- napply(names, function(x) { print(object.size(x), units = "auto") }) 

Eu adoro o script .ls.objects () de Dirk, mas continuei olhando para contar os caracteres na coluna de tamanho. Então eu fiz alguns hacks feios para torná-lo presente com formatação bonita para o tamanho:

 .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 } 

Eu faço uso agressivo do parâmetro do subset com a seleção apenas das variables ​​necessárias ao passar os frameworks de data= para o argumento data= argumento das funções de regressão. Isso resulta em alguns erros se eu esquecer de adicionar variables ​​à fórmula e ao select= vector, mas ainda economiza muito tempo devido à redução da cópia de objects e reduz significativamente o espaço ocupado pela memory. Digamos que eu tenha 4 milhões de registros com 110 variables ​​(e eu faço.) Exemplo:

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

Como forma de definir o contexto e a estratégia: a variável gdlab2 é um vetor lógico que foi construído para os sujeitos em um dataset que tinham valores normais ou quase normais para HIVfinal testes de laboratório e HIVfinal era um vetor de caracteres que resumia testes preliminares e confirmatórios para o HIV.

Isso é um bom truque.

Uma outra sugestão é usar objects eficientes de memory sempre que possível: por exemplo, use uma matriz em vez de um data.frame.

Isso realmente não aborda o gerenciamento de memory, mas uma function importante que não é amplamente conhecida é memory.limit (). Você pode aumentar o padrão usando este comando, memory.limit (size = 2500), onde o tamanho está em MB. Como o Dirk mencionou, você precisa estar usando 64 bits para aproveitar isso.

Eu gosto bastante da function de objects aprimorados desenvolvida por Dirk. Na maioria das vezes, uma saída mais básica com o nome e tamanho do object é suficiente para mim. Aqui está uma function mais simples com um objective semelhante. O uso da memory pode ser ordenado alfabeticamente ou por tamanho, pode ser limitado a um certo número de objects e pode ser ordenado ascendente ou descendente. Além disso, muitas vezes trabalho com dados que são 1 GB +, portanto, a function altera as unidades de acordo.

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

E aqui está um exemplo de saída:

 > 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 

Infelizmente eu não tive tempo para testá-lo extensivamente, mas aqui está uma dica de memory que eu não vi antes. Para mim, a memory necessária foi reduzida em mais de 50%. Quando você lê coisas em R com, por exemplo, read.csv, elas requerem uma certa quantidade de memory. Depois disso, você pode salvá-los com save("Destinationfile",list=ls()) Na próxima vez que você abrir R, poderá usar load("Destinationfile") Agora, o uso da memory pode ter diminuído. Seria bom se alguém pudesse confirmar se isso produz resultados semelhantes com um dataset diferente.

Eu nunca salve um espaço de trabalho R. Eu uso scripts de importação e scripts de dados e produzo objects de dados especialmente grandes que não quero recriar com frequência em arquivos. Desta forma eu sempre começo com um espaço de trabalho novo e não preciso limpar objects grandes. Essa é uma function muito legal.

Para ilustrar melhor a estratégia comum de reinicializações freqüentes, podemos usar o littler, que nos permite executar expressões simples diretamente da linha de comando. Aqui está um exemplo que às vezes uso para tempo BLAS diferentes para um simples crossprod.

  r -e'N<-3*10^3; M<-matrix(rnorm(N*N),ncol=N); print(system.time(crossprod(M)))' 

Da mesma forma,

  r -lMatrix -e'example(spMatrix)' 

carrega o pacote Matrix (através da opção --packages | -l) e executa os exemplos da function spMatrix. Como r sempre inicia 'fresh', este método também é um bom teste durante o desenvolvimento do pacote.

Por último, mas não menos importante, também funciona muito bem para o modo batch automatizado em scripts usando o header shebang '#! / Usr / bin / r'. Rscript é uma alternativa onde o littler não está disponível (por exemplo, no Windows).

Para fins de velocidade e memory, ao criar um grande quadro de dados por meio de uma série complexa de etapas, eu vou limpá-lo periodicamente (o dataset em andamento sendo criado) para o disco, append a qualquer coisa anterior e reiniciá-lo . Desta forma, os passos intermediários estão apenas trabalhando em pequenos frameworks de dados (o que é bom, por exemplo, o rbind diminui consideravelmente com objects maiores). O dataset inteiro pode ser lido novamente no final do processo, quando todos os objects intermediários tiverem sido removidos.

 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 ) 

Apenas para notar que as tables() do pacote data.table tables() parecem ser um bom substituto para a function customizada de .ls.objects() Dirk (detalhada em respostas anteriores), embora apenas para data.frames / tables e não eg matrices, arrays, listas.

  1. Tenho sorte e meus grandes conjuntos de dados são salvos pelo instrumento em “pedaços” (subconjuntos) de aproximadamente 100 MB (32 bits binários). Assim, posso executar etapas de pré-processamento (excluindo partes não informativas, diminuição da resolução) seqüencialmente antes de fundir o dataset.

  2. Chamar gc () “manualmente” pode ajudar se o tamanho dos dados chegar perto da memory disponível.

  3. Às vezes, um algoritmo diferente precisa de muito menos memory.
    Às vezes, há um trade-off entre a vetorização e o uso da memory.
    compare: split e lapply versus um loop for .

  4. Por uma questão de análise de dados rápida e fácil, eu geralmente trabalho primeiro com um pequeno subconjunto random ( sample () ) dos dados. Depois que o script de análise de dados / .Rnw terminar o código de análise de dados e os dados completos vão para o servidor de cálculo para o cálculo da noite / fim de semana / …

O uso de ambientes em vez de listas para manipular collections de objects que ocupam uma quantidade significativa de memory de trabalho.

O motivo: cada vez que um elemento de uma estrutura de list é modificado, a lista inteira é temporariamente duplicada. Isso se torna um problema se o requisito de armazenamento da lista é cerca de metade da memory de trabalho disponível, porque os dados precisam ser trocados para o disco rígido lento. Ambientes, por outro lado, não estão sujeitos a esse comportamento e podem ser tratados de forma semelhante às listas.

Aqui está um exemplo:

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

Em conjunto com estruturas como big.matrix ou data.table que permitem a alteração de seu conteúdo no local, um uso de memory muito eficiente pode ser alcançado.

A function ll no pacote gData pode mostrar o uso de memory de cada object.

 gdata::ll(unit='MB') 

Com apenas 4GB de RAM (rodando o Windows 10, então faça isso com cerca de 2 ou mais realisticamente 1GB) Eu tive que ter muito cuidado com a alocação.

Eu uso data.table quase exclusivamente.

A function ‘fread’ permite subconjugar informações por nomes de campo na importação; importe apenas os campos realmente necessários para começar. Se você estiver usando a base R read, anule as colunas falsas imediatamente após a importação.

Como sugere 42 , onde for possível, eu então subconjurarei dentro das colunas imediatamente após importar as informações.

Eu frequentemente rm () objects do ambiente, logo que eles não são mais necessários, por exemplo, na próxima linha depois de usá-los para subconjunto outra coisa, e chamar gc ().

‘fread’ e ‘fwrite’ do data.table podem ser muito rápidos em comparação com as leituras e escritas básicas do R.

Como kpierce8 sugere, eu quase sempre escrevo tudo fora do ambiente e o coloco de volta, mesmo com milhares / centenas de milhares de arquivos minúsculos para passar. Isso não apenas mantém o ambiente ‘limpo’ e mantém a alocação de memory baixa, mas, possivelmente devido à grave falta de RAM disponível, o R tem uma propensão a travar com frequência no meu computador; com muita frequência. Ter as informações armazenadas na própria unidade à medida que o código avança por vários estágios significa que não preciso começar desde o início se ele falhar.

A partir de 2017, acho que os SSDs mais rápidos estão rodando alguns GB por segundo através da porta M2. Eu tenho um SSD Kingston V300 (550MB / s) realmente básico de 50GB que eu uso como meu disco principal (tem Windows e R nele). Eu mantenho toda a informação em massa em um prato barato de 500GB WD. Mudo os conjuntos de dados para o SSD quando começo a trabalhar neles. Isso, combinado com ‘fread’ing e’ fwrite’ing, tem funcionado muito bem. Eu tentei usar ‘ff’, mas prefiro o primeiro. As velocidades de leitura / gravação em 4K podem criar problemas com isso; o backup de um quarto de milhão de arquivos de 1k (250MB de valor) do SSD para o prato pode levar horas. Até onde sei, ainda não existe nenhum pacote R disponível que possa otimizar automaticamente o processo de ‘chunkification’; Por exemplo, observe quanta RAM um usuário possui, teste as velocidades de leitura / gravação da RAM / todas as unidades conectadas e, em seguida, sugira um ótimo protocolo de ‘chunkification’. Isso poderia produzir algumas melhorias significativas no stream de trabalho / otimizações de resources; por exemplo, dividi-lo para … MB para o ram -> dividi-lo para … MB para o SSD -> dividi-lo para … MB no prato -> dividi-lo para … MB na fita. Poderia amostrar conjuntos de dados de antemão para dar um indicador mais realista para trabalhar.

Muitos dos problemas em que trabalhei em R envolvem a formação de pares de combinação e permutação, triplos, etc, o que apenas faz com que a limitação de RAM seja mais uma limitação, uma vez que eles, pelo menos, se expandem exponencialmente em algum momento. Isso fez com que eu focasse muita atenção na qualidade em oposição à quantidade de informação que entrava nelas, em vez de tentar limpá-la depois, e na sequência de operações na preparação das informações (a começar a operação mais simples e aumentando a complexidade); por exemplo, subconjunto, em seguida, mesclar / juntar, em seguida, formar combinações / permutações etc.

Parece haver alguns benefícios em usar a leitura R básica e escrever em alguns casos. Por exemplo, a detecção de erros em ‘fread’ é tão boa que pode ser difícil tentar obter informações realmente confusas em R para começar a limpá-las. O Base R também parece ser muito mais fácil se você estiver usando o Linux. O Base R parece funcionar bem no Linux, o Windows 10 usa ~ 20GB de espaço em disco, enquanto o Ubuntu precisa apenas de alguns GB, a RAM necessária no Ubuntu é um pouco menor. Mas notei grandes quantidades de avisos e erros ao instalar pacotes de terceiros no (L) Ubuntu. Eu não recomendo ficar muito longe de (L) Ubuntu ou outras distribuições de estoque com Linux, já que você pode perder tanto a compatibilidade geral que torna o processo quase sem sentido (eu acho que ‘unity’ deve ser cancelado no Ubuntu a partir de 2017 ). Eu percebo que isso não vai funcionar bem com alguns usuários de Linux, mas algumas das distribuições personalizadas são sem sentido além da novidade (eu passei anos usando apenas o Linux).

Espero que algumas delas ajudem os outros a sair.

Se você realmente quiser evitar os vazamentos, evite criar objects grandes no ambiente global.

O que eu costumo fazer é ter uma function que faz o trabalho e retorna NULL – todos os dados são lidos e manipulados nesta function ou outros que são chamados.

Isso não acrescenta nada ao acima, mas está escrito no estilo simples e fortemente comentado que eu gosto. Ele produz uma tabela com os objects ordenados em tamanho, mas sem alguns dos detalhes fornecidos nos exemplos acima:

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

Se você estiver trabalhando no Linux e quiser usar vários processos e só tiver que fazer operações de leitura em um ou mais objects grandes, use makeForkCluster vez de makePSOCKcluster . Isso também poupa tempo enviando o object grande para os outros processos.

Eu realmente aprecio algumas das respostas acima, seguindo @hadley e @Dirk que sugerem o fechamento de R e a emissão de sourcesource Usando uma linha de comando, eu descobri uma solução que funcionou muito bem para mim. Eu tive que lidar com centenas de espectros de massa, cada um ocupa cerca de 20 Mb de memory, então eu usei dois scripts R, como segue:

Primeiro um wrapper:

 #!/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)) } } 

Com este script eu basicamente controlo o que meu script principal faz runConsensus.r , e escrevo a resposta de dados para a saída. Com isso, cada vez que o wrapper chama o script, parece que o R é reaberto e a memory é liberada.

Espero que ajude.

Assim como as técnicas de gerenciamento de memory mais gerais dadas nas respostas acima, eu sempre tento reduzir o tamanho dos meus objects o máximo possível. Por exemplo, eu trabalho com matrizes muito grandes, mas muito esparsas, em outras palavras, matrizes onde a maioria dos valores é zero. Usando o pacote ‘Matrix’ (capitalização importante), consegui reduzir o tamanho médio dos objects de ~ 2 GB para ~ 200 MB, simplesmente como:

 my.matrix <- Matrix(my.matrix) 

O pacote Matrix inclui formatos de dados que podem ser usados ​​exatamente como uma matriz regular (não é necessário alterar seu outro código), mas são capazes de armazenar dados esparsos com muito mais eficiência, carregados na memory ou salvos no disco.

Além disso, os arquivos raw que recebo estão no formato 'long', em que cada ponto de dados possui variables x, y, z, i . Muito mais eficiente para transformar os dados em uma matriz de dimensão x * y * z com apenas a variável i .

Conheça seus dados e use um pouco de bom senso.

Você também pode obter algum benefício usando knitr e colocando seu script em chunks Rmd.

Eu costumo dividir o código em diferentes partes e selecionar qual delas salvará um ponto de verificação para armazenar em cache ou para um arquivo RDS, e

Lá você pode definir um pedaço para ser salvo em “cache”, ou você pode decidir executar ou não um pedaço em particular. Desta forma, em uma primeira execução, você pode processar apenas “parte 1”, outra execução, você pode selecionar apenas “parte 2”, etc.

Exemplo:

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

Como efeito colateral, isso também poderia lhe poupar algumas dores de cabeça em termos de reprodutibilidade 🙂

Com base na resposta de @ Dirk e @ Tony, fiz uma pequena atualização. O resultado foi outputting [1] antes dos valores de tamanho bonito, então eu tirei o capture.output que resolveu o problema:

 .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() 

Esta é uma resposta mais recente a essa excelente pergunta antiga. Do Advanced R de Hadley:

 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 )

Corrida

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

de tempos em tempos também ajuda R a liberar memory não usada, mas ainda não liberada.