2

我遇到了一些代码有点麻烦。请记住,我是一个可怕的程序员,所以我的解决方案可能不是很有说服力(可能是我内存不足的原因 - 我有4千兆字节,脚本缓慢地填满了它)。Python内存不足(使用后缀树)

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

我使用后缀树模块来封装C实现(over here)。首先我列出目录中的所有文件,然后查找两个组合,以便涵盖所有组合,我一次将两个文件传递到后缀树,然后查找常见子串的序列。

但是,我真的不知道它为什么会慢慢地耗尽内存。我希望我们可以对代码进行修改,以便以某种方式清除未使用内容的内存?显然有3,500个文件需要很长时间才能处理,但我希望可以在不增量填充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

下面是更新后的代码。我添加了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") 

回答

1

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

证据1:运行蟒蛇里面的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_bindings中定义的Python类型定义的分配和释放函数,但找不到树对象。有一个用于节点对象,但只有解除分配对象,而不是在C.

底层结构

证据3的Python包装部分:在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树对象定义应用自己的定位。

0

我不是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 

而且使用文件时,“与”关键字使用 - 它保证当你完成的文件将被关闭 - 在为打开/关闭,如果有异常抛出可能以后BARF的代码库。

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

正如我上面提到的,删除列表()在所有的找回变量的包装 - 他们已经迭代和做的list()他们可以消耗的内存TON /对运行时间大型可迭代项目。即:

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

注意: 我没有测试我在这里发布的建议更改,所以可能会出现一些小的语法错误。