Як явно можна звільнити пам'ять у Python?


387

Я написав програму Python, яка діє на великий вхідний файл, щоб створити кілька мільйонів об'єктів, що представляють трикутники. Алгоритм:

  1. читати вхідний файл
  2. обробіть файл і створіть список трикутників, представлений їх вершинами
  3. вивести вершини у форматі OFF: список вершин, а потім список трикутників. Трикутники представлені індексами до списку вершин

Вимога OFF, щоб я надрукував повний список вершин перед тим, як надрукувати трикутники, означає, що я повинен утримувати список трикутників у пам'яті, перш ніж записати вихід у файл. Тим часом я отримую помилки в пам'яті через розміри списків.

Який найкращий спосіб сказати Python, що я більше не потребую деяких даних, і їх можна звільнити?


11
Чому б не роздрукувати трикутники в проміжний файл і не прочитати їх знову, коли вони вам потрібні?
Аліса Перселл

2
Це питання потенційно може стосуватися двох зовсім різних речей. Це помилки одного і того ж процесу Python , і в цьому випадку ми дбаємо про звільнення пам’яті до купи процесу Python, чи вони є різними процесами в системі, і в цьому випадку ми дбаємо про звільнення пам'яті в ОС?
Чарльз Даффі

Відповіді:


453

Згідно з офіційною документацією Python , ви можете змусити колектор сміття звільнити невідповідну пам'ять gc.collect(). Приклад:

import gc
gc.collect()

19
У будь-якому випадку речі сміття збирають часто, за винятком деяких незвичайних випадків, тому я не думаю, що це дуже допоможе.
Леннарт Регебро

24
Загалом, gc.collect () слід уникати. Збирач сміття знає, як зробити свою роботу. Однак, якщо ОП опинилася в ситуації, коли він раптово розміщує багато об'єктів (наприклад, мільйони), gc.collect може виявитися корисним.
Джейсон Бейкер

164
Насправді виклик gc.collect()себе в кінці циклу може допомогти уникнути фрагментації пам'яті, що, в свою чергу, допомагає підтримувати продуктивність. Я бачив, що це суттєво
змінилося

38
Я використовую python 3.6. Зателефонувавши gc.collect()після завантаження фрейму даних панди з hdf5 (500k рядків), скорочення використання пам'яті з 1,7 ГБ до 500 МБ
Джон

15
Мені потрібно завантажити та обробити декілька нумерованих масивів об'ємом 25 ГБ в системі з 32 ГБ пам'яті. Використання з del my_arrayподальшим gc.collect()обробкою масиву - це єдиний спосіб, коли пам'ять фактично звільняється, і мій процес виживає, щоб завантажити наступний масив.
Девід

113

На жаль (залежно від вашої версії та випуску Python) деякі типи об’єктів використовують "безкоштовні списки", які є акуратною локальною оптимізацією, але можуть викликати фрагментацію пам'яті, зокрема, роблячи все більше пам'яті "виділеною" лише для об'єктів певного типу та тим самим недоступний до "загального фонду".

Єдиний дійсно надійний спосіб забезпечити, що велике, але тимчасове використання пам’яті НЕ повертає всі ресурси до системи, коли це зроблено, - це використовувати це в підпроцесі, який виконує роботу пам'яті. За таких умов операційна система БУДЕ виконувати свою роботу і з радістю переробляє всі ресурси, які, можливо, підробити. На щастя, multiprocessingмодуль робить подібну операцію (яка раніше була біль) не надто погано в сучасних версіях Python.

У вашому випадку використання, здається, що найкращий спосіб для підпроцесів накопичувати певні результати, але при цьому забезпечити доступність цих результатів для основного процесу - це використання напів тимчасових файлів (я маю на увазі НЕ типові файли, які автоматично закриваються, коли закриваються, просто звичайні файли, які ви явно видаляєте, коли все закінчите з ними).


31
Я впевнений, хотів би побачити тривіальний приклад цього.
Аарон Холл

3
Серйозно. Що сказав @AaronHall
Нооб Сайбот

17
Приклад @AaronHall Trivial тепер доступний , використовуючи, multiprocessing.Managerа не файли, для реалізації загального стану.
user4815162342

48

delЗаява може бути корисним, але IIRC не гарантовано , щоб звільнити пам'ять . Ці документи тут ... і чому він не позбавлений тут .

Я чув, як люди в системах типу Linux і Unix змушували процес python виконувати якусь роботу, отримуючи результати, а потім вбиваючи його.

У цій статті є примітки про збирач сміття Python, але я думаю, що недостатній контроль пам'яті - це мінус керованої пам'яті


Чи будуть IronPython та Jython ще одним варіантом уникнення цієї проблеми?
Естебан Кюбер

@voyager: Ні, не буде. І насправді жодна інша мова. Проблема полягає в тому, що він читає велику кількість даних у список, а дані занадто великі для пам'яті.
Леннарт Регебро

1
Ймовірно, буде гірше за IronPython або Jython. У таких умовах ви навіть не гарантуєте, що пам’ять буде звільнена, якщо більше нічого не містить посилання.
Джейсон Бейкер

@voyager, так, тому що віртуальна машина Java виглядає глобально, щоб пам'ять була вільною. Для JVM Jython не є нічого особливого. З іншого боку, у JVM є своя частка недоліків, наприклад, ви повинні заздалегідь задекларувати, яку велику купу він може використовувати.
Контракт професора Фолкена було порушено

32

Python збирає сміття, тому якщо ви зменшите розмір списку, він поверне пам'ять. Ви також можете використовувати оператор "del", щоб повністю позбутися змінної:

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

18
Це є і не відповідає дійсності. Хоча зменшення розміру списку дозволяє відновлювати пам'ять, немає гарантії, коли це станеться.
користувач142350

3
Ні, але зазвичай це допоможе. Однак, наскільки я розумію тут питання, проблема полягає в тому, що він повинен мати стільки об'єктів, що йому не вистачає пам'яті, перш ніж обробити їх усі, якщо він прочитає їх у список. Видалення списку перед його обробкою навряд чи буде корисним рішенням. ;)
Леннарт Регебро

3
Чи не стане стан низької пам'яті / поза пам'яттю викликати "аварійний запуск" сміттєзбірника?
Джеремі Фріснер

4
буде biglist = [] звільнити пам'ять?
neouyghur

3
так, якщо на старий список нічого іншого не посилається.
Нед Батчелдер

22

Ви не можете явно звільнити пам'ять. Що вам потрібно зробити, це переконатися, що ви не зберігаєте посилання на об’єкти. Потім вони будуть збирати сміття, звільняючи пам’ять.

У вашому випадку, коли вам потрібні великі списки, вам зазвичай потрібно реорганізувати код, як правило, використовуючи замість цього генератори / ітератори. Таким чином, вам зовсім не потрібно мати великі списки в пам'яті.

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


1
Якщо такий підхід здійсненний, то, мабуть, варто це зробити. Але слід зазначити, що ви не можете робити випадковий доступ до ітераторів, що може спричинити проблеми.
Джейсон Бейкер

Це правда, і якщо це необхідно, то для доступу до великих наборів даних випадковим чином може знадобитися якась база даних.
Леннарт Регебро

Ви можете легко використовувати ітератор для вилучення випадкового підмножини іншого ітератора.
S.Lott

Правда, але тоді вам доведеться повторити все, щоб отримати підмножину, яка буде дуже повільною.
Леннарт Регебро

21

( delможе бути вашим другом, оскільки він позначає об'єкти як видалені, коли на них немає інших посилань. Зараз часто інтерпретатор CPython зберігає цю пам'ять для подальшого використання, тому ваша операційна система може не бачити "звільненої" пам'яті.)

Можливо, ви б не зіткнулися з будь-якою проблемою пам'яті, використовуючи більш компактну структуру для своїх даних. Таким чином, списки чисел набагато менш ефективні в пам'яті, ніж формат, використовуваний стандартним arrayмодулем або стороннім numpyмодулем. Ви б економили пам’ять, розміщуючи вершини у масиві NumPy 3xN, а трикутники - у масиві N-елементів.


Так? Збір сміття CPython базується на перерахунках; це не періодична розмітка позначок (як для багатьох поширених реалізацій JVM), але натомість негайно видаляє щось, коли момент його посилання досягає нуля. Періодичне обслуговування потребує лише циклів (де знижки будуть нульовими, але їх немає через цикли в еталонному дереві). delне робить нічого, що не призначив би інше значення для всіх імен, на які посилається об'єкт.
Чарльз Даффі

Я бачу, звідки ви беретесь: відповідь оновлю відповідно. Я розумію, що інтерпретатор CPython насправді працює деяким проміжним способом: delзвільняє пам'ять з точки зору Python, але, як правило, не з точки зору бібліотеки виконання C або ОС. Посилання: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq / ... .
Ерік О Лебігот

Що стосується змісту ваших посилань, але якщо припустити, що ОП говорить про помилку, яку вони отримують від того самого процесу Python , то різниця між звільненням пам’яті від локальної купи процесу та ОС не здається актуальною ( оскільки звільнення до купи робить цей простір доступним для нових виділень у рамках цього процесу Python). І для цього delоднаково ефективний із виходами з сфери застосування, переназначення тощо
Чарльз Даффі

11

У мене була схожа проблема при читанні графіка з файлу. Обробка включала обчислення поплавкової матриці площею 200 000 x 200 000 (по одному рядку), яка не вкладалася в пам'ять. Спроба звільнити пам'ять між обчисленнями за допомогою gc.collect()виправленого аспекту проблеми, пов’язаного з пам’яттю, але це призвело до проблем із продуктивністю: я не знаю чому, але, хоча кількість використовуваної пам’яті залишалася постійною, кожен новий дзвінок gc.collect()забирав трохи більше часу, ніж попередній. Тож досить швидко збирання сміття зайняло більшу частину часу на обчислення.

Щоб виправити проблеми з пам'яттю та продуктивністю, я перейшов до використання багатопотокового трюку, який я десь прочитав (вибачте, більше не можу знайти відповідний пост). Перш ніж я читав кожен рядок файлу у великому forциклі, обробляв його та працював gc.collect()раз на час, щоб звільнити місце в пам'яті. Тепер я закликаю функцію, яка зчитує та обробляє фрагмент файлу в новому потоці. Як тільки нитка закінчується, пам'ять автоматично звільняється без дивної проблеми з продуктивністю.

Практично це працює так:

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
Цікаво, чому ви використовуєте `//` `s замість # в Python для коментарів.
JC Rocamonde

Я змішався між мовами. Дякую за зауваження, я оновив синтаксис.
Ретзод

9

Інші опублікували деякі способи, завдяки яким ви могли б примусити інтерпретатора Python звільнити пам'ять (або інакше уникнути проблем із пам'яттю). Швидше за все, ви повинні спробувати їх ідеї спочатку. Однак я вважаю важливим дати вам пряму відповідь на ваше запитання.

Насправді немає жодного способу прямо сказати Python звільнити пам'ять. Справа в тому, що якщо вам потрібен такий низький рівень контролю, вам доведеться написати розширення на C або C ++.

Однак, є кілька інструментів, які допоможуть у цьому допомогти:


3
gc.collect () та del gc.garbage [:] працюють добре, коли я використовую велику кількість пам’яті
Ендрю Скотт Еванс

3

Якщо вас не хвилює повторне використання вершин, у вас можуть бути два вихідні файли - один для вершин і один для трикутників. Потім додайте файл трикутника до вершинного файлу, коли закінчите.


1
Я думаю, що я можу зберігати лише вершини в пам'яті і друкувати трикутники у файл, а потім роздруковувати вершини лише в кінці. Однак акт написання трикутників у файл - це величезне зниження продуктивності. Чи є спосіб ускорити це ?
Натан Фелман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.