Làm thế nào tôi có thể giải phóng bộ nhớ trong Python?


387

Tôi đã viết một chương trình Python hoạt động trên một tệp đầu vào lớn để tạo ra một vài triệu đối tượng đại diện cho hình tam giác. Thuật toán là:

  1. đọc một tập tin đầu vào
  2. xử lý tệp và tạo một danh sách các hình tam giác, được biểu thị bằng các đỉnh của chúng
  3. xuất các đỉnh ở định dạng TẮT: một danh sách các đỉnh theo sau là danh sách các tam giác. Các tam giác được đại diện bởi các chỉ số vào danh sách các đỉnh

Yêu cầu TẮT mà tôi in ra danh sách đầy đủ các đỉnh trước khi tôi in ra các tam giác có nghĩa là tôi phải giữ danh sách các tam giác trong bộ nhớ trước khi tôi ghi đầu ra vào tệp. Trong khi đó, tôi đang gặp lỗi bộ nhớ do kích thước của danh sách.

Cách tốt nhất để nói với Python rằng tôi không còn cần một số dữ liệu nữa và nó có thể được giải phóng không?


11
Tại sao không in ra các hình tam giác vào một tệp trung gian và đọc lại chúng khi bạn cần chúng?
Alice Purcell

2
Câu hỏi này có khả năng là về hai điều khá khác nhau. Là những lỗi từ cùng một quy trình Python , trong trường hợp nào chúng ta quan tâm đến việc giải phóng bộ nhớ cho heap của quy trình Python, hoặc chúng là từ các quy trình khác nhau trên hệ thống, trong trường hợp nào chúng ta quan tâm đến việc giải phóng bộ nhớ cho HĐH?
Charles Duffy

Câu trả lời:


455

Theo Tài liệu chính thức của Python , bạn có thể buộc Garbage Collector giải phóng bộ nhớ không được ước tính với gc.collect(). Thí dụ:

import gc
gc.collect()

19
Mọi thứ đều là rác được thu thập thường xuyên, ngoại trừ trong một số trường hợp bất thường, vì vậy tôi không nghĩ rằng điều đó sẽ giúp ích nhiều.
Lennart Regebro

24
Nói chung, gc.collect () là phải tránh. Người thu gom rác biết cách thực hiện công việc của mình. Điều đó nói rằng, nếu OP đang ở trong một tình huống mà anh ta đột nhiên xử lý rất nhiều đối tượng (như trong hàng triệu người), gc.collect có thể tỏ ra hữu ích.
Jason Baker

165
Trên thực tế, việc gc.collect()tự gọi mình ở cuối vòng lặp có thể giúp tránh bộ nhớ bị phân mảnh, từ đó giúp duy trì hiệu suất. Tôi đã thấy điều này tạo ra sự khác biệt đáng kể (~ 20% thời gian chạy IIRC)
RobM

39
Tôi đang sử dụng python 3.6. Gọi gc.collect()sau khi tải khung dữ liệu gấu trúc từ hdf5 (500k hàng) đã giảm mức sử dụng bộ nhớ từ 1,7 GB xuống còn 500 MB
John

15
Tôi cần tải và xử lý một số mảng 25 GB gọn gàng trong một hệ thống có bộ nhớ 32 GB. Sử dụng del my_arraytheo sau bởi gc.collect()sau khi xử lý mảng là cách duy nhất bộ nhớ được giải phóng và quá trình của tôi tồn tại để tải mảng tiếp theo.
David

113

Thật không may (tùy thuộc vào phiên bản và phát hành Python của bạn), một số loại đối tượng sử dụng "danh sách miễn phí" là một tối ưu hóa cục bộ gọn gàng nhưng có thể gây ra sự phân mảnh bộ nhớ, đặc biệt bằng cách tạo ra nhiều bộ nhớ hơn cho các đối tượng thuộc một loại nhất định và do đó không có sẵn cho "quỹ chung".

Cách thực sự đáng tin cậy duy nhất để đảm bảo rằng việc sử dụng bộ nhớ lớn nhưng tạm thời KHÔNG trả lại tất cả tài nguyên cho hệ thống khi hoàn thành, đó là việc sử dụng đó xảy ra trong một quy trình con, sau đó công việc đói bộ nhớ sẽ chấm dứt. Trong điều kiện như vậy, hệ điều hành SILL thực hiện công việc của mình và sẵn sàng tái chế tất cả các tài nguyên mà quy trình con có thể đã ngấu nghiến. May mắn thay,multiprocessing mô-đun làm cho loại hoạt động này (thường là một nỗi đau) không quá tệ trong các phiên bản hiện đại của Python.

Trong trường hợp sử dụng của bạn, có vẻ như cách tốt nhất để các quy trình con tích lũy một số kết quả và đảm bảo các kết quả đó có sẵn cho quy trình chính là sử dụng các tệp bán tạm thời (ý tôi là tạm thời, KHÔNG phải là loại tệp mà tự động biến mất khi đóng, chỉ là các tệp thông thường mà bạn xóa rõ ràng khi bạn hoàn thành tất cả với chúng).


31
Tôi chắc chắn muốn thấy một ví dụ tầm thường về điều này.
Aaron Hall

3
Nghiêm túc. Những gì @AaronHall nói.
Noob Saibot

17
@AaronHall Ví dụ tầm thường hiện có sẵn , sử dụng multiprocessing.Managerthay vì các tệp để thực hiện trạng thái chia sẻ.
dùng4815162342

48

Các deltuyên bố có thể sử dụng, nhưng IIRC nó không được đảm bảo để giải phóng bộ nhớ . Các tài liệu ở đây ... và tại sao nó không được phát hành ở đây .

Tôi đã nghe mọi người trên các hệ thống Linux và Unix yêu cầu một quy trình python thực hiện một số công việc, nhận kết quả và sau đó giết chết nó.

Bài viết này có ghi chú về trình thu gom rác Python, nhưng tôi nghĩ thiếu kiểm soát bộ nhớ là nhược điểm của bộ nhớ được quản lý


IronPython và Jython có phải là một lựa chọn khác để tránh vấn đề này không?
Esteban Küber

@voyager: Không, nó sẽ không. Và cũng không có ngôn ngữ nào khác, thực sự. Vấn đề là anh ta đọc một lượng lớn dữ liệu vào một danh sách và dữ liệu quá lớn cho bộ nhớ.
Lennart Regebro

1
Nó có thể sẽ tồi tệ hơn theo IronPython hoặc Jython. Trong các môi trường đó, bạn thậm chí không được đảm bảo bộ nhớ sẽ được giải phóng nếu không có gì khác giữ tham chiếu.
Jason Baker

@voyager, vâng, vì máy ảo Java trông toàn cầu để bộ nhớ được giải phóng. Đối với JVM, Jython không có gì đặc biệt. Mặt khác, JVM có một số nhược điểm riêng, ví dụ bạn phải khai báo trước mức độ lớn mà nó có thể sử dụng.
Hợp đồng của giáo sư Falken vi phạm

32

Python được thu gom rác, vì vậy nếu bạn giảm kích thước danh sách của mình, nó sẽ lấy lại bộ nhớ. Bạn cũng có thể sử dụng câu lệnh "del" để loại bỏ hoàn toàn một biến:

biglist = [blah,blah,blah]
#...
del biglist

18
Điều này đúng và không đúng. Mặc dù việc giảm kích thước của danh sách cho phép thu hồi bộ nhớ, nhưng không có gì đảm bảo khi điều này xảy ra.
user142350

3
Không, nhưng thường thì nó sẽ giúp. Tuy nhiên, như tôi hiểu câu hỏi ở đây, vấn đề là anh ta phải có quá nhiều đồ vật mà anh ta hết bộ nhớ trước khi xử lý tất cả, nếu anh ta đọc chúng vào một danh sách. Xóa danh sách trước khi anh ta xử lý xong không chắc là một giải pháp hữu ích. ;)
Lennart Regebro

3
Không phải tình trạng bộ nhớ thấp / hết bộ nhớ sẽ kích hoạt "chạy khẩn cấp" của trình thu gom rác?
Jeremy Friesner

4
biglist = [] giải phóng bộ nhớ?
neouyghur

3
có, nếu danh sách cũ không được tham chiếu bởi bất cứ điều gì khác.
Ned Batchelder

22

Bạn không thể giải phóng bộ nhớ rõ ràng. Điều bạn cần làm là đảm bảo rằng bạn không giữ các tham chiếu đến các đối tượng. Sau đó chúng sẽ được thu gom rác, giải phóng bộ nhớ.

Trong trường hợp của bạn, khi bạn cần danh sách lớn, thông thường bạn cần tổ chức lại mã, thường sử dụng trình tạo / lặp thay thế. Bằng cách đó, bạn không cần phải có danh sách lớn trong bộ nhớ.

http://www.prasannatech.net/2009/07/intributiontion-python-generators.html


1
Nếu phương pháp này khả thi, thì có lẽ nên làm. Nhưng cần lưu ý rằng bạn không thể truy cập ngẫu nhiên trên các trình vòng lặp, điều này có thể gây ra sự cố.
Jason Baker

Điều đó đúng và nếu điều đó là cần thiết, thì việc truy cập ngẫu nhiên các bộ dữ liệu lớn có thể sẽ yêu cầu một số loại cơ sở dữ liệu.
Lennart Regebro

Bạn có thể dễ dàng sử dụng một trình vòng lặp để trích xuất một tập hợp con ngẫu nhiên của một trình vòng lặp khác.
S.Lott

Đúng, nhưng sau đó bạn sẽ phải lặp đi lặp lại mọi thứ để có được tập hợp con, sẽ rất chậm.
Lennart Regebro

21

(del có thể là bạn của bạn, vì nó đánh dấu các đối tượng là có thể xóa khi không có tài liệu tham khảo nào khác về chúng. Bây giờ, thông thường CPython giữ bộ nhớ này để sử dụng sau này, vì vậy hệ điều hành của bạn có thể không thấy bộ nhớ "giải phóng".)

Có thể bạn sẽ không gặp phải bất kỳ vấn đề bộ nhớ nào ngay từ đầu bằng cách sử dụng cấu trúc nhỏ gọn hơn cho dữ liệu của bạn. Do đó, danh sách các số ít hiệu quả bộ nhớ hơn nhiều so với định dạng được sử dụng bởi arraymô-đun tiêu chuẩn hoặc mô-đun của bên thứ ba numpy. Bạn sẽ tiết kiệm bộ nhớ bằng cách đặt các đỉnh của bạn vào một mảng NumPy 3xN và các tam giác của bạn trong một mảng phần tử N.


Hở? Bộ sưu tập rác của CPython dựa trên nền tảng; nó không phải là quét và quét định kỳ (như đối với nhiều triển khai JVM thông thường), mà thay vào đó ngay lập tức xóa một cái gì đó ngay khi số tham chiếu của nó đạt đến không. Chỉ các chu kỳ (trong đó số lần hoàn trả sẽ bằng 0 nhưng không phải do các vòng lặp trong cây tham chiếu) yêu cầu bảo trì định kỳ. delkhông làm bất cứ điều gì chỉ gán lại một giá trị khác cho tất cả các tên tham chiếu đến một đối tượng sẽ không.
Charles Duffy

Tôi thấy bạn đến từ đâu: Tôi sẽ cập nhật câu trả lời tương ứng. Tôi hiểu rằng trình thông dịch CPython thực sự hoạt động theo một cách trung gian nào đó: delgiải phóng bộ nhớ khỏi quan điểm của Python, nhưng nói chung không phải từ quan điểm của thư viện thời gian chạy C hoặc hệ điều hành. Tài liệu tham khảo: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/ ,.
Eric O Lebigot

Đồng ý với nội dung các liên kết của bạn, nhưng giả sử OP đang nói về một lỗi mà họ gặp phải từ cùng một quy trình Python , sự khác biệt giữa giải phóng bộ nhớ cho heap quy trình cục bộ và HĐH dường như không liên quan ( như giải phóng cho heap làm cho không gian đó có sẵn cho các phân bổ mới trong quy trình Python đó). Và với điều đó, delcũng hiệu quả không kém với các lối thoát hiểm từ phạm vi, đánh giá lại, v.v.
Charles Duffy

11

Tôi đã có một vấn đề tương tự trong việc đọc một biểu đồ từ một tập tin. Việc xử lý bao gồm việc tính toán ma trận float 200 000x200 000 (một dòng tại một thời điểm) không phù hợp với bộ nhớ. Cố gắng giải phóng bộ nhớ giữa các tính toán bằng cách sử dụng gc.collect()cố định khía cạnh liên quan đến bộ nhớ của vấn đề nhưng nó dẫn đến các vấn đề về hiệu năng: Tôi không biết tại sao nhưng mặc dù lượng bộ nhớ đã sử dụng không đổi, mỗi cuộc gọi mới gc.collect()mất nhiều thời gian hơn cái trước đó. Vì vậy, khá nhanh chóng việc thu gom rác mất phần lớn thời gian tính toán.

Để khắc phục cả vấn đề về bộ nhớ và hiệu năng, tôi chuyển sang sử dụng thủ thuật đa luồng mà tôi đã đọc một lần ở đâu đó (tôi xin lỗi, tôi không thể tìm thấy bài đăng liên quan nữa). Trước khi tôi đọc từng dòng của tệp trong một forvòng lặp lớn , hãy xử lý nó và chạy gc.collect()mỗi lần một lần để giải phóng không gian bộ nhớ. Bây giờ tôi gọi một hàm đọc và xử lý một đoạn của tệp trong một luồng mới. Khi luồng kết thúc, bộ nhớ sẽ tự động được giải phóng mà không gặp vấn đề hiệu năng lạ.

Thực tế nó hoạt động như thế này:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
Tôi tự hỏi tại sao bạn lại sử dụng `//` `s thay vì # trong Python cho các bình luận.
JC Rocamonde

Tôi đã lẫn lộn giữa các ngôn ngữ. Cảm ơn bạn đã nhận xét, tôi đã cập nhật cú pháp.
Retzod

9

Những người khác đã đăng một số cách mà bạn có thể "dỗ" trình thông dịch Python giải phóng bộ nhớ (hoặc nếu không tránh được vấn đề về bộ nhớ). Có thể bạn nên thử ý tưởng của họ trước. Tuy nhiên, tôi cảm thấy điều quan trọng là cung cấp cho bạn một câu trả lời trực tiếp cho câu hỏi của bạn.

Thực sự không có cách nào để nói trực tiếp với Python về bộ nhớ trống. Thực tế của vấn đề đó là nếu bạn muốn mức độ kiểm soát thấp như vậy, bạn sẽ phải viết một phần mở rộng bằng C hoặc C ++.

Điều đó nói rằng, có một số công cụ để giúp với điều này:


3
gc.collect () và del gc.garbage [:] chỉ hoạt động tốt khi tôi sử dụng một lượng lớn bộ nhớ
Andrew Scott Evans

3

Nếu bạn không quan tâm đến việc sử dụng lại đỉnh, bạn có thể có hai tệp đầu ra - một cho đỉnh và một cho hình tam giác. Sau đó nối tệp tam giác vào tệp đỉnh khi bạn hoàn thành.


1
Tôi hình tôi chỉ có thể giữ các đỉnh trong bộ nhớ và in các hình tam giác ra một tệp, sau đó chỉ in ra các đỉnh ở cuối. Tuy nhiên, hành động viết các hình tam giác vào một tệp là một sự tiêu tốn hiệu năng rất lớn. Có cách nào để tăng tốc độ đó lên?
Nathan Fellman
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.