Pandas veri çerçevesi tarafından kullanılan belleği nasıl serbest bırakırım?


111

Pandalarda açtığım gerçekten büyük bir csv dosyam var:

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

Bunu yaptığımda bellek kullanımım 2 GB artar, bu da bu dosya milyonlarca satır içerdiğinden beklenen bir durumdur. Sorunum bu hafızayı bırakmam gerektiğinde ortaya çıkıyor. Koştum ...

del df

Ancak bellek kullanımım düşmedi. Bu, bir panda veri çerçevesi tarafından kullanılan belleği serbest bırakmak için yanlış bir yaklaşım mı? Öyleyse, doğru yol nedir?


3
bu doğru, çöp toplayıcı belleği hemen serbest bırakmayabilir, ayrıca gcmodülü içe aktarabilir ve çağırabilirsiniz, gc.collect()ancak belleği kurtaramayabilir
EdChum

del dfdf oluşturulduktan sonra doğrudan çağrılmaz değil mi? Sanırım df'yi sildiğiniz noktada df'ye referanslar var. Yani silinmeyecek, bunun yerine adı silecektir.
Marlon Abeykoon

4
Çöp toplayıcı tarafından geri kazanılan belleğin aslında işletim sistemine geri verilip verilmeyeceği uygulamaya bağlıdır; Çöp toplayıcının verdiği tek garanti, geri kazanılan belleğin , işletim sisteminden daha fazla bellek istemek veya daha fazla bellek yerine, mevcut Python işlemi tarafından başka şeyler için kullanılabileceğidir .
chepner

Yaratıldıktan hemen sonra del df'yi arıyorum. Df'ye başka referans eklemedim. Tek yaptığım ipython'u açmak ve bu üç satırlık kodu çalıştırmaktı. Aynı kodu, çok fazla bellek alan başka bir nesnede çalıştırırsam, örneğin uyuşuk bir dizi gibi. del nparray mükemmel çalışıyor
b10hazard

@ b10hazard: df = ''Kodunuzun sonundaki gibi bir şeye ne dersiniz ? Dataframe tarafından kullanılan RAM'i temizliyor gibi görünüyor.
jibounet

Yanıtlar:


120

Python'da bellek kullanımını azaltmak zordur çünkü Python, belleği işletim sistemine geri vermez . Nesneleri silerseniz, bellek yeni Python nesneleri tarafından kullanılabilir, ancak free()sisteme geri dönmez ( bu soruya bakın ).

Sayısal sayısal dizilere sadık kalırsanız, bunlar serbest bırakılır, ancak kutulu nesneler serbest bırakılmaz.

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

Dataframe Sayısını Azaltma

Python belleğimizi yüksek filigranda tutar, ancak oluşturduğumuz toplam veri çerçevesi sayısını azaltabiliriz. Veri çerçevenizi değiştirirken, inplace=Truekopya oluşturmamak için tercih edin .

Diğer bir yaygın sorun, ipython'da önceden oluşturulmuş veri çerçevelerinin kopyalarını tutmaktır:

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 OutGeçmişinizi temizlemek için yazarak bunu düzeltebilirsiniz . Alternatif olarak, ipython'un ne kadar geçmişle kalacağını ayarlayabilirsiniz ipython --cache-size=5(varsayılan 1000'dir).

Dataframe Boyutunu Azaltma

Mümkün olan her yerde nesne tiplerini kullanmaktan kaçının.

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

Nesne dtype içeren değerler kutu içine alınmıştır, bu da numpy dizisinin yalnızca bir işaretçi içerdiği ve veri çerçevenizdeki her değer için yığın üzerinde tam bir Python nesnesine sahip olduğunuz anlamına gelir. Bu dizeleri içerir.

Numpy dizilerde sabit boyutlu dizeleri desteklerken, pandalar desteklemez ( kullanıcının kafasının karışmasına neden olur ). Bu, önemli bir fark yaratabilir:

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

Dize sütunlarını kullanmaktan kaçınmak veya dize verilerini sayı olarak temsil etmenin bir yolunu bulmak isteyebilirsiniz.

Birçok tekrarlanan değer içeren bir veri çerçeveniz varsa (NaN çok yaygındır), bellek kullanımını azaltmak için seyrek bir veri yapısı kullanabilirsiniz:

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

Bellek Kullanımını Görüntüleme

Bellek kullanımını görüntüleyebilirsiniz ( belgeler ):

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

Pandalar 0.17.1'den itibaren, df.info(memory_usage='deep')nesneler dahil bellek kullanımını görmek için de yapabilirsiniz .


2
Bu, 'Kabul Edilen Cevap' olarak işaretlenmelidir. Kısaca ama açıkça, python'un gerçekten ihtiyacı olmasa bile belleğe nasıl sahip olduğunu açıklıyor. Hafıza tasarrufu için ipuçları mantıklı ve kullanışlıdır. Başka bir ipucu olarak, 'çoklu işlem' kullanmayı ekleyeceğim (@ Ami'nin cevabında açıklandığı gibi.
pedram bashiri

46

Yorumlarda belirtildiği gibi, denenecek bazı şeyler var: gc.collect(@EdChum) örneğin bazı şeyleri temizleyebilir. En azından deneyimlerime göre, bu şeyler bazen işe yarıyor ve çoğu zaman yaramıyor.

Bununla birlikte, her zaman işe yarayan bir şey vardır, çünkü bu, dil düzeyinde değil, işletim sisteminde yapılır.

Orta derecede büyük bir DataFrame oluşturan ve daha küçük bir sonuç döndüren bir işleve sahip olduğunuzu varsayalım (bu bir DataFrame de olabilir):

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

O zaman böyle bir şey yaparsan

import multiprocessing

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

Ardından işlev farklı bir işlemde yürütülür . Bu işlem tamamlandığında, işletim sistemi kullandığı tüm kaynakları geri alır. Python'un, pandaların, çöp toplayıcının bunu durdurmak için yapabileceği hiçbir şey yok.


1
@ b10hazard Pandalar olmasa bile, Python belleğinin pratikte nasıl çalıştığını asla tam olarak anlamadım. Bu kaba teknik, güvendiğim tek şey.
Ami Tavory

9
Gerçekten iyi çalışıyor. Ancak bir ipython ortamında (jupyter not defteri gibi), ortaya çıkan işlemden kurtulmak için havuzu .close () ve .join () veya .terminate () yapmanız gerektiğini fark ettim. Python 3.3'ten beri bunu yapmanın en kolay yolu, bağlam yönetimi protokolünü kullanmaktır: with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])bu, bir kez yapıldığında havuzu kapatmaktır.
Zertrin

2
Bu iyi çalışıyor, sadece görev tamamlandıktan sonra sonlandırmayı ve havuza katılmayı unutmayın.
Andrey Nikishaev

1
Bir python nesnesinden belleğin nasıl geri alınacağına dair birkaç kez okuduktan sonra, bunu yapmanın en iyi yolu bu gibi görünüyor. Bir işlem oluşturun ve bu işlem sonlandırıldığında işletim sistemi belleği serbest bırakır.
muammar

1
Belki birisine, Havuzu oluştururken işlemi serbest bırakmak ve iş bittikten sonra yeni bir tane oluşturmak için maxtasksperchild = 1 kullanmayı denemek yardımcı olabilir.
giwiro

22

Bu benim için hafızayı serbest bırakma sorununu çözüyor !!!

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

veri çerçevesi açıkça null olarak ayarlanacaktır


1
Veri çerçeveleri neden [[df_1, df_2]] alt listesine eklendi? Belirli bir neden var mı? Lütfen açıkla.
goks

5
Neden son iki ifadeyi kullanmıyorsun? İlk iki ifadeye ihtiyacın olduğunu sanmıyorum.
spacedustpi

3

del dfdfsilme anında herhangi bir referans varsa silinmeyecektir. Bu nedenle del df, hafızayı serbest bırakmak için tüm referansları silmeniz gerekir .

Dolayısıyla, çöp toplamayı tetiklemek için df'ye bağlı tüm örnekler silinmelidir.

Nesnelerin üzerinde hangisinin tuttuğunu kontrol etmek için objgragh kullanın .


bağlantı objgraph'a ( mg.pov.lt/objgraph ) işaret ediyor , bir objgragh yoksa cevabınızda bir yazım hatası var
SatZ

1

Pandalarda bellek tahsisini etkileyen glibc ile ilgili bir sorun var gibi görünüyor: https://github.com/pandas-dev/pandas/issues/2659

Bu sorunla ilgili ayrıntılı olarak açıklanan maymun yaması benim için sorunu çözdü:

# 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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.