Python исчерпывает память (используя деревья суффиксов)

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

Вот в чем проблема. У меня около 3500 файлов в каталоге. Каждый файл состоит из одной строки, в которой может быть относительно мало или много символов без пробелов (самый маленький файл имеет размер 200 байт, а самый большой — 1,3 мегабайта). То, что я пытаюсь сделать, это найти общую подстроку между этими двумя файлами за раз заданной длины (в приведенном ниже коде это 13 символов). Я делаю два за раз, потому что я не ищу общую подстроку среди всех них, а скорее комбинации двух, пока все файлы не будут сравнены. То есть любая общая подстрока заданной длины между файлами, а не подстрока, общая для всех них.

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

Тем не менее, я действительно не знаю, почему у него медленно заканчивается память. Я надеюсь, что мы можем внести поправку в код, чтобы он каким-то образом очищал память от неиспользуемых вещей? Очевидно, что 3500 файлов будут долго обрабатываться, но я надеюсь, что можно обойтись без инкрементального заполнения 4 гигабайт памяти. Любая помощь будет принята с благодарностью! Вот код, который у меня есть до сих пор:

from suffix_tree import GeneralisedSuffixTree
from itertools import combinations
import glob, hashlib, os

alist = open('tmp.adjlist', 'w')

def read_f(f):
    f = open(f, "r")
    s = str(f.readlines())
    f.close()
    return s

def read_gs(a,b):
    s1 = read_f(a)
    s2 = read_f(b)
    print str(a) + ":" + str(hashlib.md5(s1).hexdigest()) + " --- " + str(b) + ":" + str(hashlib.md5(s2).hexdigest())
    return [s1,s2]

def build_tree(s):
    hlist = []

    stree = GeneralisedSuffixTree(s)

    for shared in stree.sharedSubstrings(13):
        for seq,start,stop in shared:
            hlist.append(hashlib.md5(stree.sequences[seq]).hexdigest())
        hlist = list(set(hlist))
        for h in hlist:
            alist.write(str(h) + " ")
        alist.write('\n')

glist = []

for g in glob.glob("*.g"):
    glist.append(g)

for a,b in list(combinations(glist, 2)):
    s = read_gs(a,b)
    build_tree(s)

alist.close()

os.system("uniq tmp.adjlist network.adjlist && rm tmp.adjlist")

ОБНОВЛЕНИЕ №1

Вот обновленный код. Я добавил предложения Пирса. Однако после того, как jogojapan обнаружил утечку памяти в коде C, и учитывая, что это выходит за рамки моего опыта, я в конечном итоге выбрал гораздо более медленный подход. Если кто-то хорошо разбирается в этой области, мне было бы очень любопытно посмотреть, как изменить код C, чтобы исправить утечку памяти или функцию освобождения, поскольку я думаю, что привязка дерева суффиксов C для Python очень ценна. Вероятно, потребуется несколько дней, чтобы запустить данные через этот скрипт без дерева суффиксов, поэтому я определенно готов посмотреть, есть ли у кого-нибудь творческое исправление!

from itertools import combinations
import glob, hashlib, os

def read_f(f):
    with open(f, "r") as openf:
        s = str(openf.readlines())
    return s

def read_gs(a,b):
    s1 = read_f(a)
    s2 = read_f(b)
    print str(a) + ":" + str(hashlib.md5(s1).hexdigest()) + " --- " + str(b) + ":" + str(hashlib.md5(s2).hexdigest())
    return [s1,s2]

def lcs(S1, S2):
    M = [[0]*(1+len(S2)) for i in xrange(1+len(S1))]
    longest, x_longest = 0, 0
    for x in xrange(1,1+len(S1)):
        for y in xrange(1,1+len(S2)):
            if S1[x-1] == S2[y-1]:
                M[x][y] = M[x-1][y-1] + 1
                if M[x][y]>longest:
                    longest = M[x][y]
                    x_longest  = x
            else:
                M[x][y] = 0
    return S1[x_longest-longest: x_longest]

glist = glob.glob("*.g")

for a,b in combinations(glist, 2):
    s = read_gs(a,b)
    p = lcs(s[0],s[1])
    if p != "" and len(p) >= 13:
        with open("tmp.adjlist", "a") as openf:
            openf.write(hashlib.md5(s[1]).hexdigest() + " " + hashlib.md5(s[0]).hexdigest() + "\n")

os.system("uniq tmp.adjlist network.adjlist && rm tmp.adjlist")

person mstcamus    schedule 18.07.2012    source источник


Ответы (2)


Я достаточно уверен, что в используемом вами пакете дерева суффиксов есть утечка памяти.

Доказательство 1: запуск Python внутри valgrind, а затем запуск этого простого скрипта Python

from suffix_trees import SuffixTree
t = SuffixTree("mississippi")
t = None

сообщил об этой утечке:

==8800== 1,413 (32 direct, 1,381 indirect) bytes in 1 blocks are definitely lost in loss record 1,265 of 1,374
==8800==    at 0x4A0884D: malloc (vg_replace_malloc.c:263)
==8800==    by 0xBE70AEC: make_helper (suffix_tree.c:193)
==8800==    by 0xBE704B2: SuffixTree_init (python_bindings.c:240)
==8800==    by 0x3F98C9867B: ??? (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98C49A7D: PyObject_Call (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98CD71D6: PyEval_CallObjectWithKeywords (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98C5EB45: ??? (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98C49A7D: PyObject_Call (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98CD93F2: PyEval_EvalFrameEx (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98CDDB2E: PyEval_EvalCodeEx (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98C6D7B5: ??? (in /usr/lib64/libpython2.7.so.1.0)
==8800==    by 0x3F98C49A7D: PyObject_Call (in /usr/lib64/libpython2.7.so.1.0)

Доказательство 2. Если вы посмотрите на код в файлах suffix_tree.c и python_bindings.c, вы обнаружите функцию make_helper(), которая выделяет память для дерева суффиксов (используя malloc), но не одиночный free в любом месте кода. Я специально просмотрел функции выделения и освобождения, определенные для типов Python, определенных в python_bindings, но не смог найти ничего для объекта дерева. Существует один для объектов узла, но он освобождает только часть объекта-оболочки Python, а не базовую структуру в C.

Доказательство 3: к определению типа данных для объекта Python в python_bindings.c прилагается говорящий комментарий:

/* FIXME: deallocation of this guy! */
static PyTypeObject SuffixTreeType = {
    PyObject_HEAD_INIT(NULL)
    .tp_name      = "_suffix_tree.SuffixTree",
    /* ... */
    .tp_new       = SuffixTree_new,
};

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

Поскольку вам, вероятно, не нужна зависимость между узлом и деревом для ваших целей, вы также можете применить свое собственное исправление, добавив функцию освобождения в определение объекта дерева в python_bindings.c.

person jogojapan    schedule 19.07.2012

Я не уверен на 100%, что логика вашего исходного кода делает именно то, что вы намеревались, и определенно есть постоянно растущий список, где, я думаю, вы хотели сбросить. Переменная hlist продолжала добавляться, не зацикливаясь на «общем доступе». Создание набора, локального для этого цикла, устранило эту проблему. Кроме того, вам не нужно перечислять набор списка, просто используйте набор для начала и итератор по этому набору. Я бы посоветовал узнать больше об итерируемых объектах в Python, из которых почти все объекты, содержащие данные Python, (итерируемые). В основном вам не нужно перечислять() эти объекты, если вам не нужен определенный порядок, и даже тогда вы обычно просто используете sort().

Приведенное ниже build_tree решает эти проблемы и должно значительно уменьшить объем используемой памяти — и, надеюсь, остановить ее рост и рост.

def build_tree(s):
    stree = GeneralisedSuffixTree(s)

    for shared in stree.sharedSubstrings(13):
        hset = set()
        for seq,start,stop in shared:
            hset.add(hashlib.md5(stree.sequences[seq]).hexdigest())

        for h in hset:
            alist.write(str(h) + " ")
        alist.write('\n')
        # Request cleanup on finished data
        del hset
    # Request cleanup on finished data
    del stree

Также используйте ключевое слово «с» при работе с файлами — оно гарантирует, что файл будет закрыт, когда вы закончите, — тогда как открытие/закрытие может привести к краху кодовой базы позже, если возникнет исключение.

def read_f(f):
    with open(f, "r") as openf:
        s = str(openf.readlines())
    return s

И, как я упоминал выше, удалите обертки list() для всех переменных, которые вы возвращаете - они уже являются итерируемыми, и выполнение list() для них может потреблять ТОННУ памяти/времени выполнения для больших итерируемых элементов. то есть:

for a,b in list(combinations(glist, 2)):

должно быть:

for a,b in combinations(glist, 2):

а также

glist = []
for g in glob.glob("*.g"):
    glist.append(g)

должно просто стать:

glist = glob.glob("*.g")

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

ПРИМЕЧАНИЕ. Я не проверял предлагаемые изменения, опубликованные здесь, поэтому здесь и там могут быть незначительные синтаксические ошибки.

person Pyrce    schedule 19.07.2012