Quelles astuces les gens utilisent-ils pour gérer la mémoire disponible d'une session R interactive? J'utilise les fonctions ci-dessous [basées sur les contributions de Petr Pikal et David Hinds à la liste de r-help en 2004] pour répertorier (et/ou trier) les objets les plus volumineux et pour occasionnellement rm()
certains d'entre eux. Mais, de loin, la solution la plus efficace consistait à… fonctionner sous Linux 64 bits avec une mémoire suffisante.
Y a-t-il d'autres astuces que les gens veulent partager? Un par poste, s'il vous plaît.
# 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)
}
Pour illustrer davantage la stratégie commune des redémarrages fréquents, nous pouvons utiliser littler qui nous permet d’exécuter des expressions simples directement à partir de la ligne de commande. Voici un exemple que j’utilise parfois pour chronométrer différents BLAS pour un produit croisé simple.
r -e'N<-3*10^3; M<-matrix(rnorm(N*N),ncol=N); print(system.time(crossprod(M)))'
Également,
r -lMatrix -e'example(spMatrix)'
charge le package Matrix (via le commutateur --packages | -l) et exécute les exemples de la fonction spMatrix. Comme r est toujours "frais", cette méthode est également un bon test lors du développement du paquet.
Enfin et surtout, r fonctionne également très bien pour le mode de traitement par lots automatisé dans les scripts utilisant l’en-tête Shebang '#!/Usr/bin/r'. Rscript est une alternative lorsque littler est indisponible (par exemple sous Windows).
Assurez-vous d’enregistrer votre travail dans un script reproductible. De temps en temps, rouvrez R, puis source()
votre script. Vous supprimerez tout ce que vous n'utiliserez plus et, comme avantage supplémentaire, vous aurez testé votre code.
J'utilise le package data.table . Avec son opérateur :=
, vous pouvez:
Aucune de ces opérations ne copie le (potentiellement grand) data.table
du tout, pas une seule fois.
data.table
utilise beaucoup moins de mémoire de travail.Liens connexes :
Vu ceci sur un post Twitter et pense que c'est une fonction géniale de Dirk! Pour faire suite à la réponse de JD Long, je voudrais faire ceci pour une lecture conviviale:
# 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()
Ce qui donne quelque chose comme ce qui suit:
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
NOTE: La partie principale que j'ai ajoutée était (encore une fois, adaptée de la réponse de JD):
obj.prettysize <- napply(names, function(x) {
print(object.size(x), units = "auto") })
Je fais un usage agressif du paramètre subset
en ne sélectionnant que les variables requises lors de la transmission d'images de données à l'argument data=
des fonctions de régression. Cela entraîne certaines erreurs si j'oublie d'ajouter des variables à la fois à la formule et au vecteur select=
, mais cela permet néanmoins de gagner beaucoup de temps en raison de la diminution de la copie d'objets et de réduire considérablement l'encombrement de la mémoire. Supposons que j'ai 4 millions d'enregistrements avec 110 variables (et moi si.) Exemple:
# 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")
) )
En guise de contexte et de stratégie, la variable gdlab2
est un vecteur logique construit pour les sujets d'un jeu de données contenant toutes les valeurs normales ou presque normales pour un ensemble de tests de laboratoire et HIVfinal
vecteur de caractères qui résume les tests préliminaires et de confirmation du VIH.
J'adore le script .ls.objects () de Dirk, mais j'ai continué à plisser les yeux pour compter les caractères dans la colonne de taille. J'ai donc fait quelques trucs laids pour le rendre présent avec un joli formatage pour la taille:
.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
}
C'est un bon truc.
Une autre suggestion consiste à utiliser autant que possible des objets économes en mémoire: utilisez par exemple une matrice plutôt qu'un data.frame.
Cela ne concerne pas vraiment la gestion de la mémoire, mais une fonction importante peu connue est memory.limit (). Vous pouvez augmenter la valeur par défaut à l'aide de cette commande, memory.limit (size = 2500), où la taille est exprimée en Mo. Comme Dirk l'a mentionné, vous devez utiliser 64 bits pour en tirer un avantage réel.
J'aime beaucoup la fonction améliorée des objets développée par Dirk. La plupart du temps cependant, une sortie plus basique avec le nom et la taille de l'objet me suffit. Voici une fonction plus simple avec un objectif similaire. L'utilisation de la mémoire peut être ordonnée par ordre alphabétique ou par taille, peut être limitée à un certain nombre d'objets et peut être ordonnée par ordre croissant ou décroissant. De plus, je travaille souvent avec des données de 1 Go +, alors la fonction change d'unité en conséquence.
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))
}
Et voici un exemple de sortie:
> 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
Malheureusement, je n'ai pas eu le temps de le tester de manière approfondie, mais voici un conseil mémoire que je n'ai jamais vu auparavant. Pour moi, la mémoire requise a été réduite de plus de 50%. Lorsque vous lisez des éléments dans R avec, par exemple, read.csv, ils nécessitent une certaine quantité de mémoire. Après cela, vous pourrez les enregistrer avec save("Destinationfile",list=ls())
lors de votre prochaine ouverture de R, vous pourrez utiliser load("Destinationfile")
Il est possible que l'utilisation de la mémoire ait diminué. Ce serait bien si quelqu'un pouvait confirmer si cela produisait des résultats similaires avec un jeu de données différent.
Je ne sauve jamais un espace de travail R. J'utilise des scripts d'importation et des scripts de données et génère tous les objets de données particulièrement volumineux que je ne souhaite pas recréer souvent dans des fichiers. De cette façon, je commence toujours par un nouvel espace de travail et je n'ai pas besoin de nettoyer les gros objets. C'est une très belle fonction cependant.
Pour des raisons de rapidité et de mémoire, lors de la construction d'un bloc de données volumineux via une série d'étapes complexes, je le viderai périodiquement (le fichier en cours de construction en cours de création) sur le disque, en ajoutant à tout ce qui était auparavant, puis en le redémarrant. . De cette façon, les étapes intermédiaires ne fonctionnent que sur de petites trames de données (ce qui est bien, par exemple, rbind ralentit considérablement avec des objets plus volumineux). L'ensemble du jeu de données peut être relu à la fin du processus, lorsque tous les objets intermédiaires ont été supprimés.
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 )
Il suffit de noter que le tables()
du paquet data.table
semble être un très bon substitut pour la fonction personnalisée .ls.objects()
de Dirk (détaillée dans les réponses précédentes), bien que cela ne concerne que les données.frames/tables et pas p. Ex. matrices, tableaux, listes.
J'ai la chance et mes grands ensembles de données sont sauvegardés par l'instrument en "morceaux" (sous-ensembles) d'environ 100 Mo (32 bits en binaire). Ainsi, je peux effectuer des étapes de pré-traitement (suppression de parties non informatives, sous-échantillonnage) séquentiellement avant de fusionner le jeu de données.
L'appel de gc ()
"à la main" peut aider si la taille des données se rapproche de la mémoire disponible.
Parfois, un algorithme différent nécessite beaucoup moins de mémoire.
Il existe parfois un compromis entre la vectorisation et l’utilisation de la mémoire.
compare: split
& lapply
par rapport à une boucle for
.
Par souci d’analyse rapide et facile des données, je travaille souvent d’abord avec un petit sous-ensemble aléatoire (sample ()
) des données. Une fois que le script d'analyse de données/.Rnw est terminé, le code d'analyse de données et les données complètes sont acheminés vers le serveur de calcul pour un calcul nuit/week-end/....
Utilisation d'environnements au lieu de listes pour gérer des collections d'objets occupant une quantité importante de mémoire de travail.
La raison: chaque fois qu'un élément de la structure list
est modifié, la liste entière est dupliquée temporairement. Cela devient un problème si les besoins en stockage de la liste correspondent à environ la moitié de la mémoire de travail disponible, car les données doivent ensuite être échangées sur le disque dur lent. Les environnements, en revanche, ne sont pas soumis à ce comportement et peuvent être traités comme des listes.
Voici un exemple:
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))
En conjonction avec des structures telles que big.matrix
ou data.table
permettant de modifier leur contenu sur place, une utilisation très efficace de la mémoire peut être obtenue.
La fonction ll
dans le package gData
peut également afficher l'utilisation de la mémoire de chaque objet.
gdata::ll(unit='MB')
Si vous voulez vraiment éviter les fuites, évitez de créer de gros objets dans l'environnement global.
Ce que je fais habituellement est d’avoir une fonction qui effectue le travail et retourne NULL
- toutes les données sont lues et manipulées dans cette fonction ou dans d’autres qu’elle appelle.
Cela n’ajoute rien à ce qui précède, mais est écrit dans le style simple et très commenté que j’aime. Cela donne un tableau avec les objets ordonnés en taille, mais sans certains des détails donnés dans les exemples ci-dessus:
#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]))),])
C'est une réponse plus récente à cette excellente vieille question. De Hadley's Advanced R:
install.packages("pryr")
library(pryr)
object_size(1:10)
## 88 B
object_size(mean)
## 832 B
object_size(mtcars)
## 6.74 kB
Si vous travaillez sur Linux et que vous souhaitez utiliser plusieurs processus et que vous devez uniquement faire lire des opérations sur un ou plusieurs objets volumineux utilisez makeForkCluster
au lieu de makePSOCKcluster
. Cela vous fait également gagner du temps lors de l'envoi de l'objet volumineux aux autres processus.
J'apprécie vraiment certaines des réponses ci-dessus, après @hadley et @Dirk, qui suggèrent de fermer R et d'émettre source
et d'utiliser la ligne de commande pour proposer une solution qui a très bien fonctionné pour moi. J'ai eu à traiter avec des centaines de spectres de masse, chacun occupant environ 20 Mo de mémoire, j'ai donc utilisé deux scripts R, comme suit:
D'abord un 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))
}
}
avec ce script, je contrôle fondamentalement ce que mon script principal fait runConsensus.r
, et j'écris la réponse de données pour la sortie. Avec cela, chaque fois que l'encapsuleur appelle le script, il semble que le R soit rouvert et que la mémoire soit libérée.
J'espère que ça aide.
Astuce pour traiter des objets nécessitant un calcul intermédiaire lourd: Lors de l'utilisation d'objets nécessitant beaucoup de calculs lourds et d'étapes intermédiaires, je trouve souvent utile d'écrire un bloc de code avec la fonction pour créer le object, puis un morceau de code séparé qui me donne l'option de générer et de sauvegarder l'objet en tant que fichier rmd
ou de le charger en externe à partir d'un fichier rmd
que j'avais déjà enregistré. Ceci est particulièrement facile à faire dans R Markdown
en utilisant la structure de code-morceau suivante.
```{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); }
```
Avec cette structure de code, tout ce que j'ai à faire est de changer LOAD
selon que je souhaite générer et enregistrer l'objet ou le charger directement à partir d'un fichier enregistré existant. (Bien sûr, je dois le générer et le sauvegarder la première fois, mais j'ai ensuite la possibilité de le charger.) Le réglage de LOAD = TRUE
contourne l'utilisation de ma fonction compliquée et évite tous les calculs lourds qu'il contient. Cette méthode nécessite toujours assez de mémoire pour stocker l'objet d'intérêt, mais vous évite de le calculer à chaque fois que vous exécutez votre code. Pour les objets qui nécessitent beaucoup de calculs lourds d'étapes intermédiaires (par exemple, pour les calculs impliquant des boucles sur de grands tableaux), cela peut économiser beaucoup de temps et de calcul.
Outre les techniques de gestion de la mémoire plus générales données dans les réponses ci-dessus, j'essaie toujours de réduire autant que possible la taille de mes objets. Par exemple, je travaille avec des matrices très volumineuses mais très rares, autrement dit des matrices où la plupart des valeurs sont nulles. En utilisant le package 'Matrix' (la capitalisation est importante), j'ai été en mesure de réduire la taille moyenne des objets de ~ 2 Go à ~ 200 Mo aussi simplement que:
my.matrix <- Matrix(my.matrix)
Le paquet Matrix comprend des formats de données qui peuvent être utilisés exactement comme une matrice ordinaire (inutile de changer d’autre code), mais sont capables de stocker beaucoup plus efficacement des données éparses, qu’elles soient chargées en mémoire ou sauvegardées sur disque.
De plus, les fichiers bruts que je reçois sont au format "long", où chaque point de données a des variables x, y, z, i
. Beaucoup plus efficace pour transformer les données en un tableau de dimensions x * y * z
avec uniquement la variable i
.
Connaissez vos données et utilisez un peu de bon sens.
Vous pouvez également obtenir des avantages en utilisant knitr et en insérant votre script dans Rmd Chuncks.
En général, je divise le code en plusieurs morceaux et je sélectionne celui qui enregistrera un point de contrôle dans le cache ou dans un fichier RDS.
Là-bas, vous pouvez définir un bloc à enregistrer en "cache", ou vous pouvez décider d'exécuter ou non un bloc particulier. De cette manière, lors d'une première exécution, vous ne pouvez traiter que la "partie 1", une autre exécution vous permettant de sélectionner uniquement la "partie 2", etc.
Exemple:
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)
```
Comme effet secondaire, cela pourrait également vous éviter quelques maux de tête en termes de reproductibilité :)
Basé sur les réponses de @ Dirk et de Tony, j'ai apporté une légère mise à jour. Le résultat était la sortie de [1]
avant les jolies valeurs de taille. J'ai donc sorti le capture.output
qui a résolu le problème:
.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()
J'essaie de limiter la quantité d'objets lorsque je travaille dans un projet plus important comportant de nombreuses étapes intermédiaires. Donc, au lieu de créer de nombreux objets uniques appelés
dataframe
-> step1
-> step2
-> step3
-> result
raster
-> multipliedRast
-> meanRastF
-> sqrtRast
-> resultRast
Je travaille avec des objets temporaires que j'appelle temp
.
dataframe
-> temp
-> temp
-> temp
-> result
Ce qui me laisse moins de fichiers intermédiaires et plus de vue d'ensemble.
raster <- raster('file.tif')
temp <- raster * 10
temp <- mean(temp)
resultRast <- sqrt(temp)
Pour économiser davantage de mémoire, je peux simplement supprimer temp
lorsqu'il n'est plus nécessaire.
rm(temp)
Si j'ai besoin de plusieurs fichiers intermédiaires, j'utilise temp1
, temp2
, temp3
.
Pour tester, j'utilise test
, test2
, ...
Fonctionnement
for (i in 1:10)
gc(reset = T)
de temps en temps, R aide également à libérer de la mémoire non utilisée mais restante libérée.