2

我在一些代码上遇到了一些麻烦。请记住,我是一个糟糕的程序员,所以我的解决方案可能不是很有说服力(这可能是我内存不足的原因 - 我有 4 GB 并且脚本会慢慢填充它)。

这就是问题所在。我在一个目录中有大约 3,500 个文件。每个文件由一行组成,该行可以包含相对较少或许多没有空格的字符(最小的文件为 200 字节,而最大的文件为 1.3 兆字节)。我想要做的是在设定长度的两个文件之间找到一个公共子字符串(在下面的代码中它是 13 个字符)。我一次做两个,因为我不是在所有这些中寻找一个共同的子字符串,而是在比较所有文件之前两个的组合。即,文件之间设置长度的任何公共子字符串,而不是所有文件共有的子字符串。

我使用了一个包装了 C 实现的后缀树模块(在这里)。首先,我列出目录中的所有文件,然后查找两个文件的组合以覆盖所有组合,一次将两个文件传递给后缀树,然后查找作为公共子字符串的序列。

但是,我真的不知道为什么它会慢慢耗尽内存。我希望我们可以对代码进行修改,以便它以某种方式清除未使用内容的内存?显然,处理 3,500 个文件需要很长时间,但我希望无需增量填充 4 GB 内存就可以做到。任何帮助将不胜感激!这是我到目前为止的代码:

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

这是更新的代码。我添加了 Pyrce 提出的建议。然而,在 jogojapan 发现 C 代码中的内存泄漏之后,鉴于这超出了我的专业知识范围,我最终采用了一种慢得多的方法。如果有人在这方面知识渊博,我真的很想知道如何修改 C 代码以修复内存泄漏或释放函数,因为我认为 Python 的 C 后缀树绑定非常有价值。在没有后缀树的情况下通过这个脚本运行数据可能需要几天时间,所以我绝对愿意看看是否有人有创造性的修复!

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")
4

2 回答 2

1

我有理由相信您使用的后缀树包内部存在内存泄漏。

证据 1:在 valgrind 中运行 python,然后运行这个简单的 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_bindings 中定义的 Python 类型定义的分配和释放函数,但找不到树对象的任何函数。有一个用于节点对象,但这只会释放对象的 Python 包装器部分,而不是 C 中的底层结构。

证据 3: python_bindings.c 中 Python 对象的数据类型定义附有一条明显的注释:

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

建议:您可能需要联系包的作者并让他们意识到问题。根据网页上的信息,他们已经在研究解决问题的方法,即理想情况下,树本身与其包含的节点对象之间应该存在循环依赖关系——这是一个相关问题,并且可能是程序的原因目前不执行任何解除分配。

由于您的目的可能不需要节点到树的依赖关系,因此您还可以通过在 python_bindings.c 中的树对象定义中添加一个释放函数来应用您自己的修复。

于 2012-07-19T02:26:01.657 回答
0

我不能 100% 确定您的原始代码的逻辑是否完全符合您的意图——而且肯定有一个不断增长的列表,我认为您打算在其中重置。变量 hlist 不断被添加,而不是在“共享”的循环中。在该循环本地设置一个集合解决了这个问题。此外,您不需要列出一组列表,只需使用一个集合开始并迭代该集合。我建议更多地了解 Python 中的可迭代对象——其中几乎所有 Python 数据保存对象都是(可迭代的)。基本上你不需要 list() 那些对象,除非你需要一个特定的排序,即使那样你通常只使用 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

在使用文件时还要使用'with'关键字——它保证文件在你完成后会被关闭——如果抛出异常,打开/关闭可能会在以后阻止代码库。

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

正如我上面提到的,删除所有返回的变量的 list() 包装器——它们已经是可迭代的,并且对它们执行 list() 可能会消耗大量的内存/运行时间来处理大型可迭代项。IE:

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 代码更有可能是罪魁祸首。

注意: 我没有测试我在此处发布的建议更改,因此此处和此处可能存在轻微的语法错误。

于 2012-07-19T00:34:02.167 回答