memory-management cannot - Trucos para gestionar la memoria disponible en una sesión R




allocate vector (21)

Me gusta bastante la función de objetos mejorada desarrollada por Dirk. Sin embargo, la mayoría de las veces, una salida más básica con el nombre y el tamaño del objeto es suficiente para mí. Aquí hay una función más simple con un objetivo similar. El uso de la memoria se puede ordenar alfabéticamente o por tamaño, se puede limitar a un cierto número de objetos y se puede ordenar ascendente o descendente. Además, a menudo trabajo con datos de 1 GB +, por lo que la función cambia las unidades en consecuencia.

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

Y aquí hay un ejemplo de salida:

> 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

¿Qué trucos utilizan las personas para gestionar la memoria disponible de una sesión interactiva de R? Utilizo las siguientes funciones [basadas en las publicaciones de Petr Pikal y David Hinds en la lista de ayuda en 2004] para enumerar (y / o clasificar) los objetos más grandes y, ocasionalmente, rm() algunos de ellos. Pero, con mucho, la solución más efectiva fue ... ejecutar bajo Linux de 64 bits con amplia memoria.

¿Algún otro truco que la gente quiera compartir? Una por publicación, 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)
}

Para ilustrar más a fondo la estrategia común de reinicios frecuentes, podemos usar littler que nos permite ejecutar expresiones simples directamente desde la línea de comandos. Aquí hay un ejemplo que a veces utilizo para sincronizar diferentes BLAS para un simple crossprod.

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

Igualmente,

 r -lMatrix -e'example(spMatrix)'

carga el paquete Matrix (a través del interruptor --packages | -l) y ejecuta los ejemplos de la función spMatrix. Como r siempre comienza de nuevo, este método también es una buena prueba durante el desarrollo del paquete.

Por último, pero no menos importante, también funciona bien para el modo por lotes automatizado en scripts que usan el encabezado shebang '#! / Usr / bin / r'. Rscript es una alternativa donde littler no está disponible (por ejemplo, en Windows).


Esta es una respuesta más reciente a esta excelente pregunta anterior. De la avanzada 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 )


Ese es un buen truco.

Otra sugerencia es utilizar objetos eficientes en la memoria siempre que sea posible: por ejemplo, use una matriz en lugar de un data.frame.

Esto no aborda realmente la administración de la memoria, pero una función importante que no es ampliamente conocida es memory.limit (). Puede aumentar el valor predeterminado utilizando este comando, memory.limit (tamaño = 2500), donde el tamaño es en MB. Como mencionó Dirk, debe utilizar 64 bits para poder aprovechar esto realmente.


Solo para tener en cuenta que las tables() del paquete data.table tables() parecen ser un reemplazo bastante bueno para la función personalizada .ls.objects() Dirk (detallada en respuestas anteriores), aunque solo para data.frames / tables y no, por ejemplo, matrices, matrices, liza.


Me encanta el script .ls.objects () de Dirk pero seguí entrecerrando los ojos para contar los caracteres en la columna de tamaño. Así que hice algunos trucos feos para presentarlos con un formato bonito para el tamaño:

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

Si está trabajando en Linux y desea usar varios procesos y solo tiene que realizar operaciones de lectura en uno o más objetos grandes, use makeForkCluster lugar de makePSOCKcluster . Esto también le ahorra tiempo al enviar el objeto grande a los otros procesos.


Basándome en las respuestas de @ Dirk y @ Tony, he hecho una pequeña actualización. El resultado fue la salida [1] antes de los valores de tamaño bonito, así que saqué la capture.output que resolvió el 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()

  1. Tengo la suerte de que el instrumento guarda mis grandes conjuntos de datos en "fragmentos" (subconjuntos) de aproximadamente 100 MB (binario de 32 bits). Por lo tanto, puedo realizar pasos de preprocesamiento (eliminar partes no informativas, submuestreo) de forma secuencial antes de fusionar el conjunto de datos.

  2. Llamar a gc () "a mano" puede ayudar si el tamaño de los datos se acerca a la memoria disponible.

  3. A veces un algoritmo diferente necesita mucha menos memoria.
    A veces hay un intercambio entre vectorización y uso de memoria.
    comparar: split & lapply vs. a for loop.

  4. Por el simple y rápido análisis de datos, a menudo trabajo primero con un pequeño subconjunto aleatorio ( sample () ) de los datos. Una vez que el script de análisis de datos / .Rnw haya finalizado el código de análisis de datos y los datos completos vayan al servidor de cálculo para el cálculo nocturno / de fin de semana / ...


Con solo 4 GB de RAM (ejecutando Windows 10, así que haga aproximadamente 2 o más realistas de 1 GB) he tenido que tener mucho cuidado con la asignación.

Yo uso data.table casi exclusivamente.

La función 'fread' le permite subcontratar información por nombres de campos en la importación; solo importa los campos que realmente se necesitan para empezar. Si está utilizando la base R de lectura, anule las columnas espurias inmediatamente después de la importación.

Como lo sugiere 42- , siempre que sea posible, me subcontrataré dentro de las columnas inmediatamente después de importar la información.

Con frecuencia, rm () objetos del entorno tan pronto como ya no son necesarios, por ejemplo, en la siguiente línea después de usarlos para subcontratar otra cosa, y llamar a gc ().

'fread' y 'fwrite' de data.table pueden ser muy rápidos en comparación con la base R lee y escribe.

Como sugiere kpierce8 , casi siempre escribo todo fuera del entorno y lo vuelvo a filtrar, incluso con miles / cientos de miles de archivos pequeños para transmitir. Esto no solo mantiene el entorno "limpio" y mantiene baja la asignación de memoria sino que, posiblemente debido a la gran falta de RAM disponible, R tiene una propensión a fallar con frecuencia en mi computadora; con mucha frecuencia Hacer una copia de seguridad de la información en la propia unidad a medida que el código avanza a través de varias etapas significa que no tengo que empezar desde el principio si se bloquea.

A partir de 2017, creo que los SSD más rápidos están funcionando a unos pocos GB por segundo a través del puerto M2. Tengo un SSD Kingston V300 (550MB / s) de 50GB que uso como mi disco principal (tiene Windows y R en él). Guardo toda la información a granel en una bandeja WD barata de 500GB. Muevo los conjuntos de datos al SSD cuando comienzo a trabajar en ellos. Esto, combinado con 'fread' y 'fwrite', todo ha funcionado muy bien. He intentado usar 'ff' pero prefiero el primero. Sin embargo, las velocidades de lectura / escritura de 4K pueden crear problemas con esto; realizar copias de seguridad de un cuarto de millón de archivos 1k (250 MB de valor) desde el SSD a la bandeja puede llevar horas. Que yo sepa, aún no hay ningún paquete R disponible que pueda optimizar automáticamente el proceso de "chunkification"; por ejemplo, mire cuánta RAM tiene un usuario, pruebe las velocidades de lectura / escritura de la RAM / todas las unidades conectadas y luego sugiera un protocolo de "chunkification" óptimo. Esto podría producir algunas mejoras significativas en el flujo de trabajo / optimizaciones de recursos; por ejemplo, divídalo a ... MB para el ram -> divídalo a ... MB para el SSD -> divídalo a ... MB en el disco -> divídalo a ... MB en la cinta. Podría muestrear conjuntos de datos de antemano para darle una varilla de medición más realista para trabajar.

Muchos de los problemas en los que he trabajado en R involucran la formación de pares de combinación y permutación, triples, etc., lo que hace que la limitación de RAM sea una limitación, ya que a menudo se expandirán exponencialmente en algún momento. Esto me ha hecho centrar mucha atención en la calidad en lugar de la cantidad de información que ingresa, en lugar de tratar de limpiarla después, y en la secuencia de operaciones en la preparación de la información para comenzar (comenzando con La operación más simple y aumentando la complejidad); por ejemplo, subconjunto, luego fusionar / unir, luego formar combinaciones / permutaciones, etc.

Parece que hay algunos beneficios al usar la lectura y escritura de la base R en algunos casos. Por ejemplo, la detección de errores dentro de 'fread' es tan buena que puede ser difícil tratar de obtener información realmente desordenada en R para comenzar a limpiarla. Base R también parece ser mucho más fácil si estás usando Linux. La Base R parece funcionar bien en Linux, Windows 10 usa ~ 20 GB de espacio en disco, mientras que Ubuntu solo necesita unos pocos GB, la memoria RAM necesaria con Ubuntu es ligeramente inferior. Pero he notado grandes cantidades de advertencias y errores al instalar paquetes de terceros en (L) Ubuntu. No recomendaría desviarse demasiado de (L) Ubuntu u otras distribuciones de stock con Linux, ya que puede perder tanta compatibilidad general que hace que el proceso sea casi inútil (creo que la "unidad" se cancelará en Ubuntu a partir de 2017). ). Me doy cuenta de que esto no funcionará bien con algunos usuarios de Linux, pero algunas de las distribuciones personalizadas no tienen sentido más allá de la novedad (he pasado años usando Linux solo).

Esperemos que algo de eso pueda ayudar a otros.


Además de las técnicas de administración de memoria más generales que se dan en las respuestas anteriores, siempre trato de reducir el tamaño de mis objetos en la medida de lo posible. Por ejemplo, trabajo con matrices muy grandes pero muy dispersas, es decir, matrices donde la mayoría de los valores son cero. Al usar el paquete 'Matrix' (las mayúsculas son importantes) pude reducir el tamaño promedio de mis objetos de ~ 2GB a ~ 200MB simplemente como:

my.matrix <- Matrix(my.matrix)

El paquete Matrix incluye formatos de datos que se pueden usar exactamente como una matriz regular (no es necesario cambiar su otro código), pero son capaces de almacenar datos dispersos de manera mucho más eficiente, ya sea cargados en la memoria o guardados en el disco.

Además, los archivos en bruto que recibo están en formato 'largo' donde cada punto de datos tiene las variables x, y, z, i . Mucho más eficiente para transformar los datos en una matriz de dimensión x * y * z con solo la variable i .

Conozca sus datos y use un poco de sentido común.


La función ll en el paquete gData puede mostrar el uso de memoria de cada objeto.

gdata::ll(unit='MB')

Tanto para la velocidad como para la memoria, al crear un marco de datos de gran tamaño a través de una serie de pasos complejos, lo vaciaré periódicamente (el conjunto de datos en curso que se está construyendo) en el disco, añadiéndolo a todo lo que vino antes y luego lo reiniciaré. . De esta manera, los pasos intermedios solo funcionan en marcos de datos más pequeños (lo cual es bueno porque, por ejemplo, rbind se ralentiza considerablemente con los objetos más grandes). Se puede volver a leer todo el conjunto de datos al final del proceso, cuando se hayan eliminado todos los objetos intermedios.

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 )

Asegúrese de grabar su trabajo en un script reproducible. De vez en cuando, vuelva a abrir R, luego source() su script. Limpiará todo lo que ya no esté usando y, como beneficio adicional, habrá probado su código.


Yo uso el paquete data.table . Con su := operador puedes:

  • Añadir columnas por referencia
  • Modificar subconjuntos de columnas existentes por referencia y por grupo por referencia
  • Eliminar columnas por referencia

Ninguna de estas operaciones copia la data.table (potencialmente grande) en absoluto, ni siquiera una vez.

  • La agregación también es particularmente rápida porque data.table utiliza mucha menos memoria de trabajo.

Enlaces relacionados :


Nunca guardo un espacio de trabajo R Utilizo scripts de importación y scripts de datos y muestro cualquier objeto de datos especialmente grande que no quiero recrear a menudo en archivos. De esta manera, siempre comienzo con un nuevo espacio de trabajo y no necesito limpiar objetos grandes. Esa es una función muy bonita sin embargo.


Desafortunadamente, no tuve tiempo de probarlo extensivamente, pero aquí hay un consejo de memoria que no he visto antes. Para mí la memoria requerida se redujo en más del 50%. Cuando lees cosas en R con, por ejemplo, read.csv, requieren una cierta cantidad de memoria. Después de esto, puede guardarlos con save("Destinationfile",list=ls()) La próxima vez que abra R, puede usar load("Destinationfile") Ahora el uso de la memoria puede haber disminuido. Sería bueno si alguien pudiera confirmar si esto produce resultados similares con un conjunto de datos diferente.


Hago un uso agresivo del parámetro del subset con la selección de solo las variables requeridas al pasar los marcos de data= a los data= argumento de las funciones de regresión. Si me olvido de agregar variables tanto a la fórmula como al select= vector, se producen algunos errores, pero aún así se ahorra mucho tiempo debido a la disminución de la copia de objetos y se reduce significativamente la huella de memoria. Digamos que tengo 4 millones de registros con 110 variables (y lo hago). Ejemplo:

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

A modo de establecer el contexto y la estrategia: la variable gdlab2 es un vector lógico que se construyó para los sujetos en un conjunto de datos que tenía todos los valores normales o casi normales para un montón de pruebas de laboratorio y HIVfinal era un vector de caracteres que resumía las pruebas preliminares y confirmatorias para el VIH.


Esto no agrega nada a lo anterior, pero está escrito en el estilo simple y muy comentado que me gusta. Produce una tabla con los objetos ordenados en tamaño, pero sin algunos de los detalles dados en los ejemplos anteriores:

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

¡Vio esto en una publicación de Twitter y creo que es una función increíble de Dirk! Siguiendo la respuesta de JD Long, haría esto para una lectura fácil de usar:

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

Lo que resulta en algo como lo siguiente:

                      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: La parte principal que agregué fue (nuevamente, adaptada de la respuesta de JD):

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

Yo recomiendo Dowser . Es muy fácil de configurar y no necesita cambios en su código. Puede ver los recuentos de objetos de cada tipo a través del tiempo, ver la lista de objetos en vivo, ver referencias a objetos en vivo, todo desde la sencilla interfaz web.

# memdebug.py

import cherrypy
import dowser

def start(port):
    cherrypy.tree.mount(dowser.Root())
    cherrypy.config.update({
        'environment': 'embedded',
        'server.socket_port': port
    })
    cherrypy.server.quickstart()
    cherrypy.engine.start(blocking=False)

Importa memdebug y llama a memdebug.start. Eso es todo.

No he probado PySizer o Heapy. Apreciaría las críticas de otros.

ACTUALIZAR

El código anterior es para CherryPy 2.X , CherryPy 3.X el método server.quickstart se eliminó y engine.start no toma la server.quickstart blocking . Así que si estás usando CherryPy 3.X

# memdebug.py

import cherrypy
import dowser

def start(port):
    cherrypy.tree.mount(dowser.Root())
    cherrypy.config.update({
        'environment': 'embedded',
        'server.socket_port': port
    })
    cherrypy.engine.start()




memory-management r