Подтвердить что ты не робот

Как освободить память после открытия файла в Python

Я открываю файл 3 ГБ в Python для чтения строк. Затем я сохраняю эти данные в словаре. Моя следующая цель - построить график с использованием этого словаря, чтобы я внимательно следил за использованием памяти.

Мне кажется, что Python загружает весь 3 ГБ файл в память, и я не могу избавиться от него. Мой код выглядит следующим образом:

with open(filename) as data:

    accounts = dict()

    for line in data:
        username = line.split()[1]
        IP = line.split()[0]

        try:
            accounts[username].add(IP)
        except KeyError:
            accounts[username] = set()
            accounts[username].add(IP)

print "The accounts will be deleted from memory in 5 seconds"
time.sleep(5)
accounts.clear()

print "The accounts have been deleted from memory"
time.sleep(5)

print "End of script"

Последние строки есть, чтобы я мог контролировать использование памяти. В памяти script используется бит более 3 ГБ. Очистка словаря составляет около 300 МБ. Когда конец script завершается, остальная часть памяти освобождается.

Я использую Ubuntu, и я отслеживал использование памяти, используя "Системный монитор" и "Свободную" команду в терминале.

Я не понимаю, почему Python нуждается в такой большой памяти после того, как я очистил словарь. Сохраняется ли файл в памяти? Если да, то как я могу избавиться от него? Это проблема с тем, что моя ОС не видит освобожденную память?

EDIT: я попытался заставить gc.collect() после освобождения словаря, безрезультатно.

EDIT2: я запускаю Python 2.7.3 на Ubuntu 12.04.LTS

EDIT3: Я понимаю, что забыл упомянуть что-то очень важное. Моя реальная проблема заключается не в том, что моя ОС не "возвращает" память, используемую Python. Впоследствии Python не повторяет использование этой памяти (он просто запрашивает больше памяти для ОС).

4b9b3361

Ответ 1

для меня это тоже не имеет смысла, и я хотел выяснить, как и почему это происходит. (я думал, что это тоже сработает!) Я воспроизвел его на своей машине - хотя и с меньшим файлом.

я увидел две дискретные проблемы здесь

  • Почему Python считывает файл в память (с ленивым чтением строки, это не должно быть?)
  • почему Python не освобождает память для системы.

Я не знаю вообще о внутренних компонентах Python, поэтому я просто сделал много веб-поиска. Все это может быть совершенно не по себе. (Я едва развился больше, были на стороне бизнеса в течение последних нескольких лет)

Чтение ленивой строки...

Я огляделся и нашел этот пост -

http://www.peterbe.com/plog/blogitem-040312-1

это из гораздо более ранней версии python, но эта строка резонировала со мной:

readlines() читает во всем файле сразу и разбивает его по строке.

то я увидел это, также старое сообщение effbot:

http://effbot.org/zone/readline-performance.htm

ключевым выводом было следующее:

Например, если у вас достаточно памяти, вы можете разложить весь файл в память, используя метод readlines.

и это:

В Python 2.2 и более поздних версиях вы можете зацикливаться на самом объекте файла. Это очень похоже на readlines (N) под обложками, но выглядит намного лучше

просмотр документов pythons для xreadlines [http://docs.python.org/library/stdtypes.html?highlight=readline#file.xreadlines]:

Этот метод возвращает то же самое, что и iter (f) Устаревший с версии 2.3: вместо этого используется для строки в файле.

это заставило меня подумать, что, возможно, происходит какое-то проклятие.

поэтому, если мы посмотрим на строки read [http://docs.python.org/library/stdtypes.html?highlight=readline#file.readlines]...

Прочитайте до EOF, используя readline(), и верните список, содержащий прочитанные строки.

и это похоже на то, что происходит здесь.

readline, однако, выглядел так, как мы хотели [http://docs.python.org/library/stdtypes.html?highlight=readline#file.readline]

Прочитайте одну целую строку из файла

поэтому я попробовал переключить это на readline, и этот процесс никогда не рос выше 40 МБ (раньше он увеличивался до 200 МБ, размер файла журнала)

accounts = dict()
data= open(filename)
for line in data.readline():
    info = line.split("LOG:")
    if len(info) == 2 :
        ( a , b ) = info
        try:
            accounts[a].add(True)
        except KeyError:
            accounts[a] = set()
            accounts[a].add(True)

Я предполагаю, что мы на самом деле не ленивы, читаем файл с конструкцией for x in data, хотя все комментарии к документам и stackoverflow предполагают, что мы есть. readline() потребляет значительно меньше памяти для меня, а realdlines потребляет примерно тот же объем памяти, что и for line in data

освобождение памяти

с точки зрения освобождения памяти, я не очень хорошо знаком с внутренними компонентами Python, но я вспоминаю, когда работал с mod_perl... если бы я открыл файл размером 500 МБ, этот ребенок Apache вырос до такого размера, если бы я освободил память, он был бы свободен только внутри этого ребенка - собранная память мусора никогда не возвращалась в ОС до тех пор, пока процесс не завершится.

поэтому я подумал над этой идеей и нашел несколько ссылок, которые предполагают, что это может произойти:

http://effbot.org/pyfaq/why-doesnt-python-release-the-memory-when-i-delete-a-large-object.htm

Если вы создаете большой объект и снова удаляете его, Python, вероятно, выпустил эту память, но задействованные распределители памяти не обязательно возвращают память в операционную систему, поэтому может показаться, что процесс Python использует гораздо больше виртуальных памяти, чем она на самом деле использует.

который был вроде старым, и впоследствии я нашел кучу случайных (принятых) патчей в python, которые предложили изменить поведение и теперь вы можете вернуть память в os (по состоянию на 2005 год, когда большинство этих патчей были отправлены и, по-видимому, одобрен).

то я нашел это сообщение http://objectmix.com/python/17293-python-memory-handling.html - и отметьте комментарий # 4

"" - Патч # 1123430: Распределитель малых объектов Python теперь возвращает арену в систему free(), когда вся память внутри арены снова не используется. До Python 2.5 арены (256 Кбайт кусков памяти) никогда не были В некоторых приложениях снижается размер виртуальной памяти, особенно долгосрочные приложения, которые время от времени временно используют большое количество небольших объектов. Обратите внимание, что когда Python возвращает арену платформы C free() нет никакой гарантии, что библиотека платформы C в свою очередь вернет эту память в операционную систему. Эффект патча заключается в том, чтобы прекратить делать это невозможным, а в тестах он, по-видимому, эффективен, по крайней мере, на системах Microsoft C и gcc. Спасибо Эван Джонсу за тяжелую работу и терпение.

Итак, с 2.4 под linux (как вы тестировали) вы действительно не всегда получите используемая память назад, в отношении множества мелких объектов, являющихся собраны.

Разница, поэтому (я думаю), вы видите, что между f.read() и f.readlines() заключается в том, что первый читает во всем файле как один большой строковый объект (т.е. не маленький объект), а последний возвращает список строк, где каждая строка является объектом python.

если конструкция "для строки в данных:" по существу обертывается readlines, а не readline, может быть, это имеет какое-то отношение к ней? возможно, это не проблема наличия одного 3GB-объекта, но вместо этого есть миллионы 30 тыс. объектов.

Ответ 2

Какую версию python вы пытаетесь сделать?

Я сделал тест на Python 2.7/Win7, и он работал, как ожидалось, память была выпущена.

Здесь я генерирую выборочные данные, подобные вашим:

import random

fn = random.randint

with open('ips.txt', 'w') as f: 
    for i in xrange(9000000):
        f.write('{0}.{1}.{2}.{3} username-{4}\n'.format(
            fn(0,255),
            fn(0,255),
            fn(0,255),
            fn(0,255),
            fn(0, 9000000),
        ))

И затем ваш script. Я заменил dict на defaultdict, потому что бросание исключений делает код медленнее:

import time
from collections import defaultdict

def read_file(filename):
    with open(filename) as data:

        accounts = defaultdict(set)

        for line in data:
            IP, username = line.split()[:2]
            accounts[username].add(IP)

    print "The accounts will be deleted from memory in 5 seconds"
    time.sleep(5)
    accounts.clear()

    print "The accounts have been deleted from memory"
    time.sleep(5)

    print "End of script"

if __name__ == '__main__':
    read_file('ips.txt')

Как вы можете видеть, память достигла 1.4G и затем была выпущена, оставив 36MB:

Memory usage with defaultdict

Используя ваш оригинальный script, я получил те же результаты, но немного медленнее:

enter image description here

Ответ 3

Есть разница между тем, когда Python выпускает память для повторного использования Python и когда она освобождает память обратно в ОС. Python имеет внутренние пулы для некоторых видов объектов, и он будет использовать их самостоятельно, но не возвращает его ОС.

Ответ 4

gc module может быть полезен, особенно функция collect. Я никогда не использовал его сам, но из документации, похоже, это может быть полезно. Я попробую запустить gc.collect() перед запуском accounts.clear().