Làm cách nào để giải phóng bộ nhớ được sử dụng bởi khung dữ liệu gấu trúc?


111

Tôi có một tệp csv thực sự lớn mà tôi đã mở bằng gấu trúc như sau ....

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

Khi tôi thực hiện việc này, mức sử dụng bộ nhớ của tôi tăng thêm 2GB, dự kiến ​​là do tệp này chứa hàng triệu hàng. Vấn đề của tôi đến khi tôi cần giải phóng bộ nhớ này. Tôi đã chạy ...

del df

Tuy nhiên, việc sử dụng bộ nhớ của tôi không giảm. Đây có phải là cách tiếp cận sai để giải phóng bộ nhớ được sử dụng bởi khung dữ liệu gấu trúc không? Nếu có, cách thích hợp là gì?


3
đó là chính xác, các bộ thu rác có thể không giải phóng bộ nhớ ngay lập tức, bạn cũng có thể nhập các gcmô-đun và gọi gc.collect()nhưng nó có thể không khôi phục lại bộ nhớ
EdChum

del dfkhông được gọi trực tiếp sau khi tạo df phải không? Tôi nghĩ rằng có các tham chiếu đến df tại thời điểm bạn xóa df. Vì vậy, nó sẽ không bị xóa thay vào đó nó sẽ xóa tên.
Marlon Abeykoon

4
Việc bộ nhớ được lấy lại bởi bộ thu gom rác có thực sự được trả lại cho HĐH hay không là tùy thuộc vào việc triển khai; đảm bảo duy nhất mà bộ thu gom rác thực hiện là bộ nhớ được lấy lại có thể được sử dụng bởi quy trình Python hiện tại cho những thứ khác thay vì yêu cầu hoặc thậm chí nhiều bộ nhớ hơn từ Hệ điều hành.
chepner

Tôi đang gọi del df ngay sau khi tạo. Tôi đã không thêm bất kỳ tham chiếu nào khác đến df. Tất cả những gì tôi làm là mở ipython và chạy ba dòng mã đó. Nếu tôi chạy cùng một đoạn mã trên một số đối tượng khác chiếm nhiều bộ nhớ, chẳng hạn như một mảng numpy. del nparray hoạt động hoàn hảo
b10hazard

@ b10hazard: Thế còn df = ''đoạn mã cuối bạn thì sao? Có vẻ như để xóa RAM được sử dụng bởi khung dữ liệu.
jibounet

Câu trả lời:


119

Giảm mức sử dụng bộ nhớ trong Python là rất khó, vì Python không thực sự giải phóng bộ nhớ trở lại hệ điều hành . Nếu bạn xóa các đối tượng, thì bộ nhớ sẽ có sẵn cho các đối tượng Python mới, nhưng không free()quay lại hệ thống ( xem câu hỏi này ).

Nếu bạn dính vào các mảng số, chúng sẽ được giải phóng, nhưng các đối tượng đóng hộp thì không.

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

Giảm số lượng khung dữ liệu

Python giữ cho bộ nhớ của chúng ta ở hình mờ cao, nhưng chúng ta có thể giảm tổng số khung dữ liệu mà chúng ta tạo. Khi sửa đổi khung dữ liệu của bạn inplace=True, bạn không nên tạo bản sao.

Một lỗi phổ biến khác đang giữ bản sao của các khung dữ liệu đã tạo trước đó trong 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}

Bạn có thể sửa lỗi này bằng cách nhập %reset Outđể xóa lịch sử của mình. Ngoài ra, bạn có thể điều chỉnh lượng lịch sử mà ipython lưu giữ vớiipython --cache-size=5 (mặc định là 1000).

Giảm kích thước khung dữ liệu

Nếu có thể, hãy tránh sử dụng các kiểu đối tượng.

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

Các giá trị với loại đối tượng được đóng hộp, có nghĩa là mảng numpy chỉ chứa một con trỏ và bạn có một đối tượng Python đầy đủ trên heap cho mọi giá trị trong khung dữ liệu của bạn. Điều này bao gồm các chuỗi.

Trong khi numpy hỗ trợ các chuỗi có kích thước cố định trong các mảng, thì gấu trúc thì không ( nó khiến người dùng nhầm lẫn ). Điều này có thể tạo ra sự khác biệt đáng kể:

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

Bạn có thể muốn tránh sử dụng cột chuỗi hoặc tìm cách biểu diễn dữ liệu chuỗi dưới dạng số.

Nếu bạn có khung dữ liệu chứa nhiều giá trị lặp lại (NaN rất phổ biến), thì bạn có thể sử dụng cấu trúc dữ liệu thưa thớt để giảm mức sử dụng bộ nhớ:

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

Xem mức sử dụng bộ nhớ

Bạn có thể xem việc sử dụng bộ nhớ ( tài liệu ):

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

Đối với pandas 0.17.1, bạn cũng có thể df.info(memory_usage='deep')xem việc sử dụng bộ nhớ bao gồm các đối tượng.


2
Điều này phải được đánh dấu là 'Câu trả lời được chấp nhận'. Nó giải thích ngắn gọn nhưng rõ ràng cách python giữ bộ nhớ ngay cả khi nó không thực sự cần. Các mẹo để tiết kiệm bộ nhớ đều hợp lý và hữu ích. Như một mẹo khác, tôi sẽ chỉ thêm bằng cách sử dụng 'đa xử lý' (như được giải thích trong câu trả lời của @ Ami.
pedram bashiri

46

Như đã lưu ý trong các nhận xét, có một số điều cần thử: gc.collect(@EdChum) có thể xóa nội dung chẳng hạn. Ít nhất từ ​​kinh nghiệm của tôi, những điều này đôi khi hiệu quả và thường thì không.

Tuy nhiên, có một thứ luôn hoạt động vì nó được thực hiện ở hệ điều hành, không phải ngôn ngữ, cấp độ.

Giả sử bạn có một hàm tạo một DataFrame khổng lồ trung gian và trả về một kết quả nhỏ hơn (cũng có thể là một DataFrame):

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

Sau đó, nếu bạn làm điều gì đó như

import multiprocessing

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

Sau đó, hàm được thực thi ở một quá trình khác . Khi quá trình đó hoàn tất, hệ điều hành sẽ lấy lại tất cả các tài nguyên mà nó đã sử dụng. Thực sự không có gì Python, gấu trúc, người thu gom rác, có thể làm để ngăn chặn điều đó.


1
@ b10hazard Ngay cả khi không có gấu trúc, tôi vẫn chưa bao giờ hiểu đầy đủ cách bộ nhớ Python hoạt động trong thực tế. Kỹ thuật thô thiển này là thứ duy nhất tôi dựa vào.
Ami Tavory

9
Hoạt động thực sự tốt. Tuy nhiên, trong môi trường ipython (như jupyter notebook), tôi thấy rằng bạn cần phải .close () và .join () hoặc .termina () pool để loại bỏ quá trình sinh sản. Cách dễ nhất để làm điều đó kể từ Python 3.3 là sử dụng giao thức quản lý ngữ cảnh: giao thức with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])này sẽ đóng nhóm sau khi hoàn thành.
Zertrin

2
Điều này hoạt động tốt, chỉ cần đừng quên chấm dứt và tham gia nhóm sau khi nhiệm vụ hoàn thành.
Andrey Nikishaev

1
Sau khi đọc nhiều lần về cách lấy lại bộ nhớ từ đối tượng python, đây có vẻ là cách tốt nhất để làm điều đó. Tạo một quy trình và khi quy trình đó bị hủy thì hệ điều hành sẽ giải phóng bộ nhớ.
muammar

1
Có thể nó giúp ích cho ai đó, khi tạo Pool, hãy thử sử dụng maxtasksperchild = 1 để giải phóng quy trình và sinh ra một quy trình mới sau khi hoàn thành công việc.
giwiro

22

Điều này giải quyết vấn đề giải phóng bộ nhớ cho tôi !!!

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

data-frame sẽ được đặt rõ ràng là null


1
Tại sao khung dữ liệu được thêm vào danh sách phụ [[df_1, df_2]]? Bất kỳ lý do cụ thể? Vui lòng giải thích.
goks

5
Tại sao bạn không chỉ sử dụng hai câu lệnh cuối cùng? Tôi không nghĩ bạn cần hai câu đầu tiên.
spacedustpi

3

del dfsẽ không bị xóa nếu có bất kỳ tham chiếu nào đến dfthời điểm xóa. Vì vậy, bạn cần xóa tất cả các tham chiếu đến nó del dfđể giải phóng bộ nhớ.

Vì vậy, tất cả các trường hợp liên kết với df nên được xóa để kích hoạt thu thập rác.

Sử dụng objgragh để kiểm tra xem cái nào đang bám vào các đối tượng.


liên kết trỏ đến objgraph ( mg.pov.lt/objgraph ), đó là lỗi đánh máy trong câu trả lời của bạn trừ khi có phản đối
SatZ 25/03 '19

1

Có vẻ như có sự cố với glibc ảnh hưởng đến việc phân bổ bộ nhớ trong Pandas: https://github.com/pandas-dev/pandas/issues/2659

Bản vá khỉ chi tiết về vấn đề này đã giải quyết vấn đề cho tôi:

# 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.