Bagaimana saya bisa secara eksplisit membebaskan memori dengan Python?

388

Saya menulis sebuah program Python yang bertindak pada file input besar untuk membuat beberapa juta objek yang mewakili segitiga. Algoritme adalah:

  1. baca file input
  2. memproses file dan membuat daftar segitiga, diwakili oleh simpulnya
  3. mengeluarkan simpul dalam format OFF: daftar simpul diikuti oleh daftar segitiga. Segitiga diwakili oleh indeks ke dalam daftar simpul

Persyaratan MATI yang saya cetak daftar lengkap simpul sebelum saya mencetak segitiga berarti bahwa saya harus memegang daftar segitiga dalam memori sebelum saya menulis output ke file. Sementara itu saya mendapatkan kesalahan memori karena ukuran daftar.

Apa cara terbaik untuk memberi tahu Python bahwa saya tidak lagi memerlukan beberapa data, dan itu bisa dibebaskan?

Nathan Fellman
sumber
11
Mengapa tidak mencetak segitiga ke file perantara, dan membacanya kembali saat Anda membutuhkannya?
Alice Purcell
2
Pertanyaan ini berpotensi tentang dua hal yang sangat berbeda. Apakah kesalahan-kesalahan dari proses Python yang sama , dalam hal ini kita peduli tentang membebaskan memori ke tumpukan proses Python, atau apakah mereka dari proses yang berbeda pada sistem, dalam hal ini kita peduli tentang membebaskan memori ke OS?
Charles Duffy

Jawaban:

456

Menurut Dokumentasi Resmi Python , Anda dapat memaksa Pengumpul Sampah untuk melepaskan memori yang tidak direferensikan gc.collect(). Contoh:

import gc
gc.collect()
Havenard
sumber
19
Toh barang-barang sering dikumpulkan, kecuali dalam beberapa kasus yang tidak biasa, jadi saya pikir itu tidak banyak membantu.
Lennart Regebro
24
Secara umum, gc.collect () harus dihindari. Pengumpul sampah tahu bagaimana melakukan tugasnya. Yang mengatakan, jika OP berada dalam situasi di mana dia tiba-tiba mendelokasi banyak objek (seperti dalam jutaan), gc.collect mungkin terbukti bermanfaat.
Jason Baker
165
Sebenarnya menelepon gc.collect()diri sendiri di akhir perulangan dapat membantu menghindari memecah-mecah memori, yang pada gilirannya membantu menjaga kinerja tetap tinggi. Saya telah melihat ini membuat perbedaan yang signifikan (~ 20% runtime IIRC)
RobM
39
Saya menggunakan python 3.6. Menelepon gc.collect()setelah memuat bingkai data panda dari hdf5 (baris 500k) mengurangi penggunaan memori dari 1,7GB menjadi 500MB
John
15
Saya perlu memuat dan memproses beberapa array numpy 25GB dalam suatu sistem dengan memori 32GB. Menggunakan del my_arraydiikuti oleh gc.collect()setelah memproses array adalah satu-satunya cara memori sebenarnya dilepaskan dan proses saya bertahan untuk memuat array berikutnya.
David
113

Sayangnya (tergantung pada versi Anda dan rilis Python) beberapa jenis objek menggunakan "daftar gratis" yang merupakan optimasi lokal yang rapi tetapi dapat menyebabkan fragmentasi memori, khususnya dengan membuat lebih banyak dan lebih banyak memori "disisihkan" untuk hanya objek dari jenis tertentu dan dengan demikian tidak tersedia untuk "dana umum".

Satu-satunya cara yang benar-benar dapat diandalkan untuk memastikan bahwa penggunaan memori yang besar namun sementara TIDAK mengembalikan semua sumber daya ke sistem ketika selesai, adalah membuat penggunaan itu terjadi dalam subproses, yang kemudian dihentikan oleh pekerjaan yang haus memori. Dalam kondisi seperti itu, sistem operasi AKAN melakukan tugasnya, dan dengan senang hati mendaur ulang semua sumber daya yang mungkin telah diproses oleh subproses. Untungnya, multiprocessingmodul ini membuat operasi semacam ini (yang dulunya agak menyebalkan) tidak terlalu buruk di versi modern Python.

Dalam kasus penggunaan Anda, tampaknya cara terbaik bagi subproses untuk mengakumulasikan beberapa hasil dan memastikan hasil tersebut tersedia untuk proses utama adalah dengan menggunakan file semi-temporer (maksudnya semi-temporer, BUKAN jenis file yang secara otomatis hilang ketika ditutup, hanya file biasa yang Anda hapus secara eksplisit ketika Anda selesai melakukannya).

Alex Martelli
sumber
31
Saya yakin ingin melihat contoh sepele dari ini.
Aaron Hall
3
Serius. Apa yang dikatakan @AaronHall.
Noob Saibot
17
Contoh @AaronHall Trivial sekarang tersedia , menggunakan multiprocessing.Managerdaripada file untuk menerapkan status bersama.
user4815162342
48

The delPernyataan mungkin digunakan, tetapi IIRC itu tidak dijamin untuk membebaskan memori . The docs di sini ... dan mengapa tidak dirilis di sini .

Saya telah mendengar orang-orang di sistem Linux dan Unix-type forking proses python untuk melakukan beberapa pekerjaan, mendapatkan hasil dan kemudian membunuhnya.

Artikel ini memiliki catatan tentang pengumpul sampah Python, tapi saya pikir kurangnya kontrol memori adalah kelemahan dari memori yang dikelola

Aiden Bell
sumber
Apakah IronPython dan Jython menjadi pilihan lain untuk menghindari masalah ini?
Esteban Küber
@voyager: Tidak, tidak akan. Dan tidak ada bahasa lain, sungguh. Masalahnya adalah ia membaca dalam jumlah besar data menjadi daftar, dan data terlalu besar untuk memori.
Lennart Regebro
1
Mungkin akan lebih buruk di bawah IronPython atau Jython. Di lingkungan itu, Anda bahkan tidak dijamin memori akan dirilis jika tidak ada yang memegang referensi.
Jason Baker
@voyager, ya, karena mesin virtual Java mencari memori secara global untuk membebaskan. Bagi JVM, Jython bukan hal yang istimewa. Di sisi lain, JVM memiliki bagian kelemahannya sendiri, misalnya Anda harus menyatakan sebelumnya berapa besar tumpukan yang bisa digunakan.
Kontrak Prof. Falken dilanggar
32

Python dikumpulkan dari sampah, jadi jika Anda mengurangi ukuran daftar Anda, itu akan mendapatkan kembali memori. Anda juga dapat menggunakan pernyataan "del" untuk menyingkirkan variabel sepenuhnya:

biglist = [blah,blah,blah]
#...
del biglist
Ned Batchelder
sumber
18
Ini benar dan tidak benar. Sementara mengurangi ukuran daftar memungkinkan memori untuk direklamasi, tidak ada jaminan kapan ini akan terjadi.
user142350
3
Tidak, tetapi biasanya itu akan membantu. Namun, seperti yang saya pahami pertanyaannya di sini, masalahnya adalah dia harus memiliki begitu banyak objek sehingga dia kehabisan memori sebelum memproses semuanya, jika dia membacanya menjadi daftar. Menghapus daftar sebelum dia selesai memproses tidak mungkin menjadi solusi yang berguna. ;)
Lennart Regebro
3
Tidakkah kondisi kehabisan memori / kehabisan memori yang rendah memicu "menjalankan darurat" dari pengumpul sampah?
Jeremy Friesner
4
akankah biglist = [] melepaskan memori?
neouyghur
3
ya, jika daftar lama tidak dirujuk oleh hal lain.
Ned Batchelder
22

Anda tidak dapat secara eksplisit membebaskan memori. Yang perlu Anda lakukan adalah memastikan Anda tidak menyimpan referensi ke objek. Mereka kemudian akan menjadi sampah yang dikumpulkan, membebaskan memori.

Dalam kasus Anda, ketika Anda membutuhkan daftar besar, Anda biasanya perlu mengatur ulang kode, biasanya menggunakan generator / iterator. Dengan begitu Anda tidak perlu memiliki daftar besar dalam memori sama sekali.

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

Lennart Regebro
sumber
1
Jika pendekatan ini layak, maka mungkin layak dilakukan. Tetapi harus dicatat bahwa Anda tidak dapat melakukan akses acak pada iterator, yang dapat menyebabkan masalah.
Jason Baker
Itu benar, dan jika itu perlu, maka mengakses dataset data besar secara acak kemungkinan memerlukan semacam database.
Lennart Regebro
Anda dapat dengan mudah menggunakan iterator untuk mengekstrak subset acak dari iterator lain.
S.Lott
Benar, tetapi kemudian Anda harus mengulangi semuanya untuk mendapatkan subset, yang akan sangat lambat.
Lennart Regebro
21

( delbisa jadi teman Anda, karena menandai objek sebagai dihapus ketika tidak ada referensi lain untuk mereka. Sekarang, sering kali juru bahasa CPython menyimpan memori ini untuk digunakan nanti, sehingga sistem operasi Anda mungkin tidak melihat memori "bebas".)

Mungkin Anda tidak akan mengalami masalah memori di tempat pertama dengan menggunakan struktur yang lebih kompak untuk data Anda. Dengan demikian, daftar angka jauh lebih sedikit memori-efisien daripada format yang digunakan oleh arraymodul standar atau modul pihak ketiga numpy. Anda akan menghemat memori dengan meletakkan simpul Anda dalam array NumPy 3xN dan segitiga Anda dalam array N-elemen.

Eric O Lebigot
sumber
Eh? Pengumpulan sampah CPython berbasis pada penghitungan ulang; ini bukan tanda-dan-sapuan berkala (seperti untuk banyak implementasi JVM umum), tetapi segera menghapus sesuatu saat jumlah referensi mencapai nol. Hanya siklus (di mana penghitungan ulang akan menjadi nol tetapi bukan karena loop di pohon referensi) memerlukan pemeliharaan berkala. deltidak melakukan apa pun yang hanya menetapkan ulang nilai yang berbeda untuk semua nama yang merujuk objek tidak.
Charles Duffy
Saya melihat dari mana Anda berasal: Saya akan memperbarui jawaban yang sesuai. Saya mengerti bahwa juru bahasa CPython sebenarnya bekerja dalam beberapa cara menengah: delmembebaskan memori dari sudut pandang Python, tetapi umumnya tidak dari sudut pandang C runtime library atau OS. Referensi: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/… .
Eric O Lebigot
Setuju dengan isi tautan Anda, tetapi dengan asumsi OP berbicara tentang kesalahan yang mereka dapatkan dari proses Python yang sama , perbedaan antara membebaskan memori ke tumpukan proses-lokal dan ke OS tampaknya tidak mungkin relevan ( karena membebaskan heap membuat ruang itu tersedia untuk alokasi baru dalam proses Python). Dan untuk itu, delsama efektifnya dengan keluar-dari-lingkup, penugasan kembali, dll
Charles Duffy
11

Saya memiliki masalah yang sama dalam membaca grafik dari file. Pemrosesan termasuk perhitungan matriks float 200 000x200 000 (satu baris pada suatu waktu) yang tidak sesuai dengan memori. Mencoba membebaskan memori di antara komputasi menggunakan gc.collect()memperbaiki aspek yang terkait dengan memori dari masalah tetapi mengakibatkan masalah kinerja: Saya tidak tahu mengapa tetapi meskipun jumlah memori yang digunakan tetap konstan, setiap panggilan baru gc.collect()membutuhkan waktu lebih lama daripada yang sebelumnya. Jadi, cukup cepat pengumpulan sampah menghabiskan sebagian besar waktu perhitungan.

Untuk memperbaiki masalah memori dan kinerja saya beralih ke penggunaan trik multithreading yang saya baca sekali di suatu tempat (maaf, saya tidak dapat menemukan posting terkait lagi). Sebelum saya membaca setiap baris file dalam satu forlingkaran besar , memprosesnya, dan menjalankannya gc.collect()sesekali untuk membebaskan ruang memori. Sekarang saya memanggil fungsi yang membaca dan memproses sepotong file di utas baru. Setelah utas berakhir, memori secara otomatis dibebaskan tanpa masalah kinerja yang aneh.

Praktisnya bekerja seperti ini:

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
Retzod
sumber
1
Saya ingin tahu mengapa Anda menggunakan `//` `s alih-alih # dengan Python untuk komentar.
JC Rocamonde
Saya terlibat dalam berbagai bahasa. Terima kasih atas komentarnya, saya perbarui sintaksisnya.
Retzod
9

Yang lain telah memposting beberapa cara yang mungkin dapat "membujuk" juru bahasa Python agar membebaskan memori (atau menghindari masalah memori). Kemungkinannya adalah Anda harus mencoba ide mereka terlebih dahulu. Namun, saya merasa penting untuk memberi Anda jawaban langsung untuk pertanyaan Anda.

Sebenarnya tidak ada cara untuk secara langsung memberitahu Python untuk membebaskan memori. Faktanya adalah bahwa jika Anda ingin tingkat kontrol yang rendah, Anda harus menulis ekstensi dalam C atau C ++.

Yang mengatakan, ada beberapa alat untuk membantu ini:

Jason Baker
sumber
3
gc.collect () dan del gc.garbage [:] berfungsi dengan baik ketika saya menggunakan memori dalam jumlah besar
Andrew Scott Evans
3

Jika Anda tidak peduli tentang penggunaan kembali verteks, Anda bisa memiliki dua file output - satu untuk simpul dan satu untuk segitiga. Kemudian tambahkan file segitiga ke file titik setelah Anda selesai.

Nosredna
sumber
1
Saya pikir saya hanya bisa menyimpan simpul dalam memori dan mencetak segitiga keluar ke file, dan kemudian mencetak simpul hanya di akhir. Namun, tindakan menulis segitiga ke file sangat menguras kinerja. Apakah ada cara untuk mempercepat itu ?
Nathan Fellman