パンダのデータフレームで使用されているメモリを解放するにはどうすればよいですか?


111

次のようにパンダで開いた非常に大きなcsvファイルがあります。

import pandas
df = pandas.read_csv('large_txt_file.txt')

これを実行すると、メモリ使用量が2GB増加します。これは、このファイルに数百万の行が含まれているためです。私の問題は、このメモリを解放する必要があるときに発生します。走った…

del df

しかし、私のメモリ使用量は減りませんでした。これは、pandasデータフレームによって使用されているメモリを解放するための間違ったアプローチですか?もしそうなら、適切な方法は何ですか?


3
正解です。ガベージコレクターはメモリをすぐに解放しない可能性がありgcます。モジュールをインポートして呼び出すこともできますがgc.collect()、メモリを回復できない可能性があります
EdChum

del dfdfの作成直後に直接呼び出されませんか?dfを削除した時点でdfへの参照があると思います。そのため、削除されず、名前が削除されます。
Marlon Abeykoon

4
ガベージコレクターによって回収されたメモリが実際にOSに返されるかどうかは、実装によって異なります。ガベージコレクターが行う唯一の保証は、OS に要求したり、さらに多くのメモリを追加したりする代わりに、現在のPythonプロセスが再利用されたメモリを他のものに使用できることです。
chepner

作成後すぐにデルDFに電話します。私はdfに他の参照を追加しませんでした。私がしたことは、ipythonを開いて、これらの3行のコードを実行することだけでした。numpy配列など、大量のメモリを必要とする他のオブジェクトで同じコードを実行すると、del nparrayは完全に動作します
b10hazard

@ b10hazard:df = ''コードの最後にあるようなものはどうですか?データフレームによって使用されるRAMをクリアするようです。
ジブネット

回答:


119

Pythonが実際にメモリを解放してオペレーティングシステムに戻すことはないため、Pythonでのメモリ使用量の削減は困難です。オブジェクトを削除すると、メモリは新しいPythonオブジェクトで使用できますfree()が、システムに戻すことはできません(この質問を参照してください)。

numpyの配列に固執する場合、それらは解放されますが、ボックス化されたオブジェクトは解放されません。

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

データフレーム数の削減

Pythonはメモリを最高水準に保ちますが、作成するデータフレームの総数を減らすことができます。データフレームを変更するときは、inplace=Trueコピーを作成しないようにしてください。

もう1つの一般的な問題は、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}

入力%reset Outして履歴をクリアすると、これを修正できます。または、ipythonが保持する履歴の量を調整することもできipython --cache-size=5ます(デフォルトは1000)。

データフレームサイズの縮小

可能な限り、オブジェクトのdtypeの使用は避けてください。

>>> df.dtypes
foo    float64 # 8 bytes per value
bar      int64 # 8 bytes per value
baz     object # at least 48 bytes per value, often more

オブジェクトdtypeの値はボックス化されます。つまり、numpy配列にはポインターが含まれているだけで、データフレームのすべての値のヒープに完全なPythonオブジェクトがあります。これには文字列が含まれます。

numpyは配列で固定サイズの文字列をサポートしますが、pandasはサポートしません(ユーザーの混乱を引き起こします)。これにより、大きな違いが生じる可能性があります。

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

文字列列の使用を避けたり、文字列データを数値として表現する方法を見つけたりしたい場合があります。

多くの繰り返し値を含むデータフレームがある場合(NaNは非常に一般的です)、スパースデータ構造を使用してメモリ使用量を削減できます。

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

メモリ使用量の表示

メモリ使用量を表示できます(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

パンダ0.17.1以降、df.info(memory_usage='deep')オブジェクトを含むメモリ使用量を確認することもできます。


2
これは「承認済みの回答」とマークする必要があります。それは簡単にしかし明確にそれが本当にそれを必要としないときでさえ、Pythonがメモリを保持する方法を説明します。メモリを節約するためのヒントは、すべて賢明で便利です。別のヒントとして、私はちょうど亜美の答え@で説明したように(「マルチプロセッシング」を使用して追加します。
pedram bashiri

46

コメントで述べたように、試してみるべきことがいくつかありますgc.collect。たとえば、(@ EdChum)は何かをクリアするかもしれません。少なくとも私の経験から、これらのことは時々機能し、しばしば機能しない。

ただし、言語レベルではなくOSレベルで行われるため、常に機能することが1つあります。

中間の巨大なDataFrameを作成し、より小さな結果(DataFrameの場合もあります)を返す関数があるとします。

def huge_intermediate_calc(something):
    ...
    huge_df = pd.DataFrame(...)
    ...
    return some_aggregate

次に、あなたが何かをした場合

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]

次に、関数は別のプロセスで実行されます。そのプロセスが完了すると、OSは使用していたすべてのリソースを再取得します。ガベージコレクターであるPython、pandasがこれを阻止するためにできることは本当にありません。


1
@ b10hazardパンダがいなくても、Pythonメモリが実際にどのように機能するかを完全に理解したことはありません。この大雑把なテクニックは、私が信頼している唯一のものです。
Ami Tavory

9
本当にうまくいきます。ただし、ipython環境(jupyterノートブックなど)では、生成されたプロセスを削除するには、プールを.close()および.join()または.terminate()する必要があることがわかりました。Python 3.3以降、これを行う最も簡単な方法は、コンテキスト管理プロトコルを使用するwith multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])ことです。
Zertrin、2017年

2
これはうまく機能します。タスクが完了した後、プールの終了と参加を忘れないでください。
Andrey Nikishaev

1
pythonオブジェクトからメモリを要求する方法について何度か読んだ後、これがそのための最良の方法のようです。プロセスを作成し、そのプロセスが強制終了されると、OSがメモリを解放します。
muammar 2018

1
多分それは誰かを助けるかもしれません、プールを作成するとき、プロセスを解放し、ジョブが完了した後に新しいものを生成するためにmaxtasksperchild = 1を使用してみてください。
giwiro

22

これは、私のためにメモリを解放する問題を解決します!!!

del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()

データフレームは明示的にnullに設定されます


1
データフレームがサブリスト[[df_1、df_2]]に追加された理由 具体的な理由は?説明してください。
18

5
最後の2つのステートメントだけを使用しませんか?最初の2つのステートメントは必要ないと思います。
spacedustpi

3

del df削除df時にへの参照がある場合は削除されません。したがってdel df、メモリを解放するには、それへのすべての参照を削除する必要があります。

そのため、ガベージコレクションをトリガーするには、dfにバインドされたすべてのインスタンスを削除する必要があります。

objgraghを使用して、オブジェクトを保持しているものを確認します。


リンクがobjgraph(mg.pov.lt/objgraph)を指している場合、objgraghがない限り、それは回答のタイプミスです
SatZ

1

Pandasのメモリ割り当てに影響を与えるglibcの問題があるようです:https : //github.com/pandas-dev/pandas/issues/2659

この問題について詳細なモンキーパッチは私のために問題を解決しました:

# monkeypatches.py

# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None

__old_del = getattr(pd.DataFrame, '__del__', None)

def __new_del(self):
    if __old_del:
        __old_del(self)
    libc.malloc_trim(0)

if libc:
    print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
    pd.DataFrame.__del__ = __new_del
else:
    print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.