Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Python: Общие вопросы > принудительная очистка памяти


Автор: kulibinka 7.2.2007, 07:02
работая в питоне с большими массивами текстов, пришел к необходимости как-то разобраться с памятью, которую при этой работе питон себе забирает.

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

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

но хотелось бы знать: как заставить питон освободить всю ненужную мне память (кроме тех переменных, которые я ему скажу)?

попытки найти хоть что-то о принудительной очистке памяти в питоне, натыкался только на фразы типа "Python never returning memory to the operating system". Т.е. расслабся, используй xrange, купляй оперативки, увеличивай файл подкачки и жди нового питона...

все действительно так мрачно, или же есть решение этой проблемы?

Автор: Daevaorn 7.2.2007, 08:43
посмотри тут http://docs.python.org/lib/module-gc.html

Автор: J2A 7.2.2007, 10:48
Возможно, есть вариант пересмотреть алгоритм обработки. Нужно ли, чтобы весь объем текст был в памяти? Если это последовательная обработка, стоит использовать итераторы/генераторы. Возможно, сериализация тоже будет вариантом - zodb/shelve? Если точнее опишешь что именно ты делаешь с текстами и как, то будет проще что-то советовать.

Автор: kulibinka 7.2.2007, 17:55
Написал пример кода, в котором я ну совершенно не понимаю куда уходит память:

Код

# -*- coding: windows-1251 -*-

from modules import *


def obj_from_file (f_name):
    """достает обьект питоновский из файла"""
    f = open(f_name, 'rb')
    return marshal.load(f)

def obj_to_file (obj, f_name):
    """заганяет обьект питоновский в файл"""
    f = open(f_name, 'wb')
    marshal.dump(obj, f)
    f.close()

def test_ute4ka(col_experementov = 100):
    """моделирую ситуацию, в которой должна быть утечка!"""    
    test_file = '1000'

    stroki = obj_from_file(test_file)
    print "v kazhdom cikle obrabativaetsja %d strok"%len(stroki)
    
    print 'start %d experementov' % col_experementov

#    а тут главный цикл
    for i in range (col_experementov):
        t_start = time.time()
        curent_info = []
        for s in stroki:
            stroka = Get_norms_from_text(s)
            curent_info.append(stroka)
        full_time = time.time() - t_start
        print i, full_time

    f = open('stat_info.csv', 'w')
    f.write("\n".join(exp_values))
    f.close()
    print "\nEND"

test_ute4ka(100)


И информация по коду и статистике:

Цитата

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

насколько я понимаю, каждый цикл не должен увеличивать память (ведь ничего нового не появляется)

а статистика показывает, что до запуска теста файл подкачки был 328 метра, перед началом цикла он дошел до 373, и потом каждый проход цикла уверенно забирал еще около метра (в конце работы файл подкачки за 100 шагов стал размером 505 метров, за 200 шагов - 630 метров)


Добавлено @ 18:08 
максимально упростил пример, утечка остается

Код

# -*- coding: windows-1251 -*-

from modules import *

def test_ute4ka(col_experementov = 100):
    """моделирую ситуацию, в которой должна быть утечка!"""    
    stroki = ['мама мыла раму, так как мама очень любила чистоту' for i in range(100)]
    
    print 'start %d experementov' % col_experementov

#    а тут главный цикл
    for i in range (col_experementov):
        t_start = time.time()
        curent_info = []
        for s in stroki:
            stroka = Get_norms_from_text(s)
            curent_info.append(stroka)
        full_time = time.time() - t_start
        print i, full_time


test_ute4ka(100)

Автор: kulibinka 7.2.2007, 18:52
новая информация: я знаю что течет мой модуль modules
потому что если вынести ф-ю Get_norms_from_text в код теста и ничего больше не использовать, то все отлично.
если же использовать ее подключая модуль modules, то начинается утечка.
узнать бы что именно в этом модуле течет...

Добавлено @ 19:06 
бррр
как так - подключаю модуль, который сам по себе работает отлично, и начинается утечка.
а не подключаю - не начинается...
причем нигде в модулях не используется ничего похожего (связанного) на те ф-и которые в этом тестовом коде используются... ну откуда она может взяться?

Автор: J2A 7.2.2007, 19:11
На вскидку:

1) Используй xrange вместо range.Почему: http://omsk.lug.ru/wacko/Python/Perfomance/PerfomanceTips#h263-15 и http://omsk.lug.ru/wacko/Python/Perfomance/GentooHacks#h258-10.
2) Зачем тебе в памяти хранить строки, если ты только и делаешь, что цикл по ним? Сделай итератор, который будешь перезапускать в каждой итерации главного цикла. Очень сильно сэкономишь память.
3) Не импортируй все из модуля. http://omsk.lug.ru/wacko/Python/Perfomance/MyTips#h448-3.
4) Вообще выкинь цикл по строкам - используй list comprehensive:
вместо
Код

curent_info = []
for s in stroki:
            stroka = Get_norms_from_text(s)
            curent_info.append(stroka)

делай так:
Код

current_info = [Get_norms_from_text(s) for s in stroki]

http://omsk.lug.ru/wacko/Python/Perfomance/PerfomanceTips#h263-7.

В общем, be Pythonic.

P.S. И переменные нормально называй. experiments_number например

Автор: kulibinka 7.2.2007, 19:16
Спасибо за советы.
Но без применения 1, 2 и 4 код отлично работает, так как если заменить ф-ю Get_norms_from_text то никакой утечки нету.
А ответ где-то видимо около 3, я там как разу кручусь - пытаюсь импортировать только используемые ф-и и посмотреть на утечку.

Добавлено @ 19:19 
а с пунктом 4 я категорически несогласен, так как предложенный Вами варианта на порядок тугодумнее. Да, он питоник и на него приятно смотреть, но работает он туго.

Автор: J2A 7.2.2007, 19:26
На порядок тугодумнее? Факты-с в студию! Пока не увижу - не поверю.

Автор: kulibinka 7.2.2007, 20:45
попытался подобрать факты - не смог smile
помню что еще в самом начале обработки больших текстовых массивов информации питоник-вариант обработки списков у меня практически всегда забивал весь файл подкачки и скрипт вылетал с ошибкой мемориеррор, но при этом отлично продолжал работать если я обрабатывал списки тупо в лоб.
я тогда решил что питоник не предусмотрен для больших-пребольших списков, и с того момента пишу все только в лоб.
но сейчас попытки вызвать ту же ошибку

Цитата

# -*- coding: windows-1251 -*-

import time

def another_function (text):
    rezult = []
    words = text.split(' ')
    for i in range(100):
  for w in words:
    rezult.append(w.lower())
    rezult.append(w.upper())
    return rezult

def func1 (stroki):
    return [another_function(s) for s in stroki]

def func2 (stroki):
    rezult = []
    for s in stroki:
  rezult.append(another_function(s))
    return rezult

def func22 (stroki):
    rezult = []
    for s in stroki:
  t = another_function(s)
  rezult.append(t)
    return rezult

def func3 (stroki):
    return map(another_function, stroki)
    


def test_function(test_function, col_experementov = 1000, message=''):
    """тест ф-й"""
    print message, 
    t1=time.time()
    stroki = ['мама мыла раму так как мама очень любила чистоту' for i in range(col_experementov)]

#    а тут главный цикл
    rezult = test_function(stroki)

    print time.time()-t1
    return rezult


test_function(func1, 10000, 'pythonic')
test_function(func2, 10000, 'v lob')
test_function(func22, 10000, 'v lob 2')
test_function(func3, 10000, 'map')


ни к чему не привели - и по времени, и по использованию файла подкачки все ф-ии работают приблизительно одинаково (за 75-80 секунд).

хотя работать в питоник-стайл оно конечно красивше smile

Добавлено @ 20:48 
кстати, я поборол утечку smile

оказывается у меня modules использовал psyco, и после убивания psyco утечка памяти прошла сама собой.

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

всем огромное спасибо за советы!

Автор: J2A 8.2.2007, 06:08
Немного гиперболизируя, ты тестируешь феррарри и трактор  по скорости. Цепляешь к обоим прицеп на полторы тонны. По скорости 10 км/ч с прицепом по грунтовке, делаешь вывод, что максимальные скорости одинаковы, только феррарри чуть покрасивше.

Код

$ python -m timeit 'res = []' 'for i in xrange(10000): res.append(i)'
100 loops, best of 3: 3.77 msec per loop
$ python -m timeit 'res = [i for i in xrange(10000)]'
1000 loops, best of 3: 1.68 msec per loop

Автор: kulibinka 8.2.2007, 13:08
абсолютно правильная аналогия
только я без прицепов от двух тонн не ездяю.

А вообще я больше люблю трактора...

Потому что в Ваше красивое ферари я вообще ничего своего вкрутить не смогу - оно цельное, без нужных мне дырок для болтов, и предназначено использоваться только как есть.

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

У меня например на все тракторы установлен джпс-навигатор, потому что без него совершенно непонятно работают ли скрипты, срок работы которых несколько часов, или же просто стоят...

А ферари там тихо мирно сопит... работает, не работает - совершенно непонятно... и через 10 минут черный экран начинает нервировать... движение есть, а прогресса нет... ну что с него возьмешь - феррари оно и в африке феррари smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)