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

注意: 我沒有測試我在這裏發佈的建議更改,所以可能會出現一些小的語法錯誤。