Bagaimana cara melepaskan memori yang digunakan oleh pandas dataframe?

111

Saya memiliki file csv yang sangat besar yang saya buka di panda sebagai berikut ....

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

Setelah saya melakukan ini, penggunaan memori saya meningkat sebesar 2GB, yang diharapkan karena file ini berisi jutaan baris. Masalah saya muncul ketika saya perlu melepaskan memori ini. Aku lari ....

del df

Namun, penggunaan memori saya tidak turun. Apakah ini pendekatan yang salah untuk melepaskan memori yang digunakan oleh bingkai data panda? Jika ya, bagaimana cara yang tepat?

b10hazard
sumber
3
itu benar, pengumpul sampah mungkin tidak langsung melepaskan memori, Anda juga dapat mengimpor gcmodul dan memanggil gc.collect()tetapi mungkin tidak memulihkan memori
EdChum
del dftidak dipanggil langsung setelah pembuatan df kan? Saya pikir ada referensi ke df pada titik Anda menghapus df tersebut. Jadi itu tidak akan dihapus, melainkan menghapus namanya.
Marlon Abeykoon
4
Apakah memori yang diklaim kembali oleh pengumpul sampah sebenarnya diberikan kembali ke OS tergantung pada implementasi; satu-satunya jaminan yang dibuat oleh pengumpul sampah adalah bahwa memori yang diperoleh kembali dapat digunakan oleh proses Python saat ini untuk hal-hal lain daripada meminta atau bahkan lebih banyak memori dari OS.
chepner
Saya menelepon del df tepat setelah pembuatan. Saya tidak menambahkan referensi lain ke df. Yang saya lakukan hanyalah membuka ipython dan menjalankan tiga baris kode itu. Jika saya menjalankan kode yang sama pada beberapa objek lain yang membutuhkan banyak memori, seperti misalnya array numpy. del nparray bekerja dengan sempurna
b10hazard
@ b10hazard: Bagaimana dengan sesuatu seperti df = ''di akhir kode Anda? Sepertinya untuk membersihkan RAM yang digunakan oleh dataframe.
jibounet

Jawaban:

120

Mengurangi penggunaan memori dengan Python itu sulit, karena Python tidak benar-benar melepaskan memori ke sistem operasi . Jika Anda menghapus objek, maka memori tersedia untuk objek Python baru, tetapi tidak free()kembali ke sistem ( lihat pertanyaan ini ).

Jika Anda tetap berpegang pada larik numpy numerik, itu dibebaskan, tetapi objek kotak tidak.

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

Mengurangi Jumlah Kerangka Data

Python menjaga memori kita pada watermark tinggi, tapi kita bisa mengurangi jumlah total dataframe yang kita buat. Saat memodifikasi kerangka data Anda, lebih baik inplace=True, jadi Anda tidak membuat salinan.

Gotcha umum lainnya memegang salinan dari dataframe yang dibuat sebelumnya di 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}

Anda dapat memperbaikinya dengan mengetik %reset Outuntuk menghapus riwayat Anda. Atau, Anda dapat menyesuaikan berapa banyak histori yang disimpan ipython ipython --cache-size=5(defaultnya adalah 1000).

Mengurangi Ukuran Dataframe

Jika memungkinkan, hindari menggunakan dtypes objek.

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

Nilai dengan objek dtype diberi kotak, yang berarti array numpy hanya berisi pointer dan Anda memiliki objek Python penuh di heap untuk setiap nilai dalam dataframe Anda. Ini termasuk string.

Sementara numpy mendukung string berukuran tetap dalam array, panda tidak ( ini menyebabkan kebingungan pengguna ). Ini dapat membuat perbedaan yang signifikan:

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

Anda mungkin ingin menghindari penggunaan kolom string, atau mencari cara untuk merepresentasikan data string sebagai angka.

Jika Anda memiliki kerangka data yang berisi banyak nilai berulang (NaN sangat umum), Anda dapat menggunakan struktur data renggang untuk mengurangi penggunaan memori:

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

Melihat Penggunaan Memori

Anda dapat melihat penggunaan memori ( dokumen ):

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

Pada pandas 0.17.1, Anda juga dapat melakukannya df.info(memory_usage='deep')untuk melihat penggunaan memori termasuk objek.

Wilfred Hughes
sumber
2
Ini harus ditandai 'Jawaban Diterima'. Ini secara singkat tetapi jelas menjelaskan bagaimana python menyimpan memori bahkan ketika tidak benar-benar membutuhkannya. Tip untuk menghemat memori semuanya masuk akal dan berguna. Sebagai tip lain saya hanya akan menambahkan menggunakan 'multiprocessing' (seperti yang dijelaskan dalam jawaban @ Ami.
pedram bashiri
46

Seperti dicatat di komentar, ada beberapa hal yang bisa dicoba: gc.collect(@EdChum) mungkin menghapus beberapa hal, misalnya. Setidaknya dari pengalaman saya, hal-hal ini terkadang berhasil dan seringkali tidak.

Ada satu hal yang selalu berhasil, karena ini dilakukan di OS, bukan bahasa, level.

Misalkan Anda memiliki fungsi yang membuat DataFrame besar menengah, dan mengembalikan hasil yang lebih kecil (yang mungkin juga berupa DataFrame):

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

Kemudian jika Anda melakukan sesuatu seperti

import multiprocessing

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

Kemudian fungsi tersebut dijalankan pada proses yang berbeda . Saat proses tersebut selesai, OS mengambil kembali semua resource yang digunakannya. Benar-benar tidak ada yang bisa dilakukan Python, panda, pengumpul sampah, untuk menghentikannya.

Ami Tavory
sumber
1
@ b10hazard Bahkan tanpa panda, saya tidak pernah sepenuhnya memahami bagaimana memori Python bekerja dalam praktiknya. Teknik kasar ini adalah satu-satunya hal yang saya andalkan.
Ami Tavory
9
Bekerja dengan sangat baik. Namun dalam lingkungan ipython (seperti notebook jupyter) saya menemukan bahwa Anda perlu .close () dan .join () atau .terminate () pool untuk menyingkirkan proses yang muncul. Cara termudah untuk melakukan itu sejak Python 3.3 adalah dengan menggunakan protokol manajemen konteks: with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])yang mengambil adalah menutup kumpulan setelah selesai.
Zertrin
2
Ini berfungsi dengan baik, jangan lupa menghentikan & bergabung dengan pool setelah tugas selesai.
Andrey Nikishaev
1
Setelah membaca beberapa kali tentang cara mengklaim kembali memori dari objek python, sepertinya ini adalah cara terbaik untuk melakukannya. Buat proses, dan ketika proses itu dihentikan maka OS melepaskan memori.
muammar
1
Mungkin itu membantu seseorang, saat membuat Pool coba gunakan maxtasksperchild = 1 untuk merilis proses dan menelurkan yang baru setelah pekerjaan selesai.
giwiro
22

Ini memecahkan masalah melepaskan memori untuk saya !!!

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

bingkai data akan secara eksplisit disetel ke nol

hardi
sumber
1
Mengapa kerangka data ditambahkan dalam sub-daftar [[df_1, df_2]]? Ada alasan khusus? Tolong jelaskan.
goks
5
Mengapa Anda tidak menggunakan dua pernyataan terakhir saja? Saya tidak berpikir Anda membutuhkan dua pernyataan pertama.
spacedustpi
3

del dftidak akan dihapus jika ada referensi ke dfsaat penghapusan. Jadi, Anda perlu menghapus semua referensi ke dalamnya dengan del dfuntuk melepaskan memori.

Jadi semua contoh yang terikat ke df harus dihapus untuk memicu pengumpulan sampah.

Gunakan objgragh untuk memeriksa mana yang menahan objek.

Marlon Abeykoon
sumber
tautan menunjuk ke objgraph ( mg.pov.lt/objgraph ), itu adalah kesalahan ketik dalam jawaban Anda kecuali ada objgragh
SatZ
1

Sepertinya ada masalah dengan glibc yang memengaruhi alokasi memori di Pandas: https://github.com/pandas-dev/pandas/issues/2659

Itu Patch monyet rinci tentang masalah ini telah diselesaikan masalah bagi saya:

# 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)
MarkNS
sumber