Il est difficile de réduire l'utilisation de la mémoire en Python, car Python ne restitue pas réellement la mémoire au système d'exploitation. . Si vous supprimez des objets, la mémoire est alors disponible pour de nouveaux objets Python, mais pas free()
de retour au système ( voir cette question ).
Si vous vous en tenez aux tableaux numériques numpy, ceux-ci sont libérés, mais pas les objets en boîte.
>>> import os, psutil, numpy as np
>>> def usage():
... process = psutil.Process(os.getpid())
... return process.get_memory_info()[0] / float(2 ** 20)
...
>>> usage() # initial memory usage
27.5
>>> arr = np.arange(10 ** 8) # create a large array without boxing
>>> usage()
790.46875
>>> del arr
>>> usage()
27.52734375 # numpy just free()'d the array
>>> arr = np.arange(10 ** 8, dtype='O') # create lots of objects
>>> usage()
3135.109375
>>> del arr
>>> usage()
2372.16796875 # numpy frees the array, but python keeps the heap big
Réduire le nombre d'images de données
Python maintient notre mémoire en filigrane, mais nous pouvons réduire le nombre total de dataframes que nous créons. Lorsque vous modifiez votre dataframe, préférez inplace=True
afin de ne pas créer de copies.
Une autre erreur courante consiste à conserver des copies de cadres de données précédemment créés dans ipython :
In [1]: import pandas as pd
In [2]: df = pd.DataFrame({'foo': [1,2,3,4]})
In [3]: df + 1
Out[3]:
foo
0 2
1 3
2 4
3 5
In [4]: df + 2
Out[4]:
foo
0 3
1 4
2 5
3 6
In [5]: Out # Still has all our temporary DataFrame objects!
Out[5]:
{3: foo
0 2
1 3
2 4
3 5, 4: foo
0 3
1 4
2 5
3 6}
Vous pouvez résoudre ce problème en tapant %reset Out
pour effacer votre historique. Alternativement, vous pouvez ajuster la quantité d'historique conservé par ipython avec ipython --cache-size=5
(la valeur par défaut est 1000).
Réduire la taille de la trame de données
Dans la mesure du possible, évitez d'utiliser des dtypes d'objets.
>>> df.dtypes
foo float64 # 8 bytes per value
bar int64 # 8 bytes per value
baz object # at least 48 bytes per value, often more
Les valeurs avec un type d'objet sont encadrées, ce qui signifie que le tableau numpy ne contient qu'un pointeur et que vous avez un objet Python complet sur le tas pour chaque valeur de votre cadre de données. Cela inclut les chaînes de caractères.
Alors que numpy supporte les chaînes de taille fixe dans les tableaux, pandas ne le fait pas ( cela a provoqué une confusion chez les utilisateurs ). Cela peut faire une différence significative :
>>> import numpy as np
>>> arr = np.array(['foo', 'bar', 'baz'])
>>> arr.dtype
dtype('S3')
>>> arr.nbytes
9
>>> import sys; import pandas as pd
>>> s = pd.Series(['foo', 'bar', 'baz'])
dtype('O')
>>> sum(sys.getsizeof(x) for x in s)
120
Vous voudrez peut-être éviter d'utiliser des colonnes de chaînes de caractères ou trouver un moyen de représenter les données de chaînes de caractères sous forme de nombres.
Si vous disposez d'un cadre de données qui contient de nombreuses valeurs répétées (NaN est très courant), vous pouvez utiliser la fonction structure de données éparses pour réduire l'utilisation de la mémoire :
>>> df1.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo float64
dtypes: float64(1)
memory usage: 605.5 MB
>>> df1.shape
(39681584, 1)
>>> df1.foo.isnull().sum() * 100. / len(df1)
20.628483479893344 # so 20% of values are NaN
>>> df1.to_sparse().info()
<class 'pandas.sparse.frame.SparseDataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo float64
dtypes: float64(1)
memory usage: 543.0 MB
Visualisation de l'utilisation de la mémoire
Vous pouvez visualiser l'utilisation de la mémoire ( docs ) :
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 14 columns):
...
dtypes: datetime64[ns](1), float64(8), int64(1), object(4)
memory usage: 4.4+ GB
À partir de la version 0.17.1 de pandas, vous pouvez également effectuer les opérations suivantes df.info(memory_usage='deep')
pour voir l'utilisation de la mémoire, y compris les objets.
7 votes
C'est exact, le ramasseur d'ordures peut ne pas libérer la mémoire immédiatement, vous pouvez également importer le fichier
gc
et appelergc.collect()
mais il ne peut pas récupérer la mémoire0 votes
del df
n'est pas appelé directement après la création de df, n'est-ce pas ? Je pense qu'il y a des références au df au moment où vous supprimez le df. Donc il ne sera pas supprimé mais le nom sera supprimé.6 votes
La question de savoir si la mémoire récupérée par le ramasseur de déchets est effectivement rendue au système d'exploitation dépend de l'implémentation ; la seule garantie que donne le ramasseur de déchets est que la mémoire récupérée peut être utilisée par le processus Python actuel pour d'autres choses au lieu de demander, voire de plus de la mémoire de l'OS.
0 votes
J'appelle del df juste après la création. Je n'ai pas ajouté d'autres références à df. Tout ce que j'ai fait, c'est ouvrir ipython et exécuter ces trois lignes de code. Si j'exécute le même code sur un autre objet qui prend beaucoup de mémoire, comme par exemple un tableau numpy, del nparray fonctionne parfaitement.
3 votes
@b10hazard : Que diriez-vous de quelque chose comme
df = ''
à la fin de votre code ? Il semble que cela efface la mémoire vive utilisée par le cadre de données.0 votes
df = ''
fonctionne pour moi