【问题标题】:Python Memory error solutions if permanent access is required如果需要永久访问,Python 内存错误解决方案
【发布时间】:2012-06-06 15:04:35
【问题描述】:

首先,我知道关于 SO 的 Python 内存错误问题的数量,但到目前为止,没有一个与我的用例相匹配。

我目前正在尝试解析一堆文本文件(约 6k 文件,约 30 GB)并存储每个唯一的单词。是的,我正在建立一个单词表,不,我不打算用它做坏事,这是为了大学。

我将找到的单词列表实现为一个集合(使用words = set([]) 创建,与words.add(word) 一起使用),我只是将每个找到的单词添加到其中,考虑到集合机制应该删除所有重复项。

这意味着我需要永久访问整个集合才能使其工作(或者至少我没有其他选择,因为必须在每次插入时检查整个列表是否存在重复项)。

现在,MemoryError 大约运行了 25%,而它使用了大约 3.4 GB 的 RAM。我使用的是 32 位 Linux,所以我知道这个限制来自哪里,而且我的电脑只有 4 Gigs 的 RAM,所以即使是 64 位也无济于事。

我知道复杂性可能很糟糕(每次插入可能 O(n),虽然我不知道 Python 集是如何实现的(树?)),但它仍然(可能)更快并且(肯定)比将每个单词添加到原始列表并随后删除重复项更节省内存。

有没有办法让它运行?我预计大约 6-10 GB 的唯一词,所以使用我当前的 RAM 是不可能的,并且升级我的 RAM 目前是不可能的(一旦我开始让这个脚本在大量文件上松散,就不能很好地扩展) .

目前我唯一的想法是在磁盘上缓存(这会进一步减慢进程),或者将临时集写入磁盘并在之后合并它们,这将花费更多时间,而且复杂性确实很可怕。是否有一种解决方案不会导致可怕的运行时间?

为了记录,这是我的完整来源。因为它是为个人使用而写的,它非常可怕,但你明白了。

import os
import sys
words=set([])
lastperc = 0
current = 1
argl = 0
print "Searching for .txt-Files..."
for _,_,f in os.walk("."):
    for file in f:
        if file.endswith(".txt"):
            argl=argl+1
print "Found " + str(argl) + " Files. Beginning parsing process..."
print "0%                                              50%                                             100%"
for r,_,f in os.walk("."):
    for file in f:
        if file.endswith(".txt"):
            fobj = open(os.path.join(r,file),"r")
            for line in fobj:
                line = line.strip()
                word, sep, remains = line.partition(" ")
                if word != "":
                    words.add(word)
                word, sep, remains = remains.partition(" ")
                while sep != "":
                    words.add(word)
                    word, sep, remains2 = remains.partition(" ")
                    remains = remains2
                if remains != "":
                    words.add(remains)
            newperc = int(float(current)/argl*100)
            if newperc-lastperc > 0:
                for i in range(newperc-lastperc):
                    sys.stdout.write("=")
                    sys.stdout.flush()
            lastperc = newperc
            current = current+1
print ""
print "Done. Set contains " + str(len(words)) + " different words. Sorting..."
sorteddic = sorted(words, key=str.lower)
print "Sorted. Writing to File"
print "0%                                              50%                                             100%"
lastperc = 0
current = 1
sdicl = len(sorteddic)-1
fobj = open(sys.argv[1],"w")
for element in sorteddic:
    fobj.write(element+"\n")
    newperc = int(float(current)/sdicl*100)
    if newperc-lastperc > 0:
        for i in range(newperc-lastperc):
            sys.stdout.write("=")
            sys.stdout.flush()
    lastperc = newperc
    current = current+1
print ""
print "Done. Enjoy your wordlist."

感谢您的帮助和想法。

【问题讨论】:

  • 您对line.partition(" ") 的使用似乎可以通过使用简单的line.split() 来简化......但我可能会遗漏一些东西。
  • @mgilson 是的,看起来像。看来我把那部分代码搞砸了。

标签: python memory python-2.7


【解决方案1】:

您可能需要将密钥存储在磁盘上。像Redis 这样的键值对存储可能符合要求。

【讨论】:

  • 你也可以使用 pickle 或 sqlite3 吗?
【解决方案2】:

您真的是指 6-10GB 的唯一词吗?这是英文文本吗?当然,即使算上专有名词和名称,唯一单词也不应该超过几百万。

无论如何,我要做的是一次处理一个文件,甚至一次处理一个文件的一个部分(例如 100k),然后为该部分构建一个唯一的单词表。然后将所有集合合并为后处理步骤。

【讨论】:

  • 我的预测来自前 25% 使用的空间,它可能会更低。而且,正如您在代码中看到的那样,目前我无法删除特殊字符,所以“hi”、“hi”。 "hi,", ... 都将被视为唯一词,这会使字典进一步膨胀。不过感谢您的想法,我可能会这样做。
【解决方案3】:

我倾向于使用数据库表,但如果您想留在单个框架内,请查看 PyTables:http://www.pytables.org/moin

【讨论】:

    【解决方案4】:

    我尝试的第一件事是将单词限制为小写字符 - 正如 Tyler Eaves 指出的那样,这可能会减小集合大小以适应内存。这是一些非常基本的代码:

    import os
    import fnmatch
    import re
    
    def find_files(path, pattern):
        for root, files, directories in os.walk(path):
            for f in fnmatch.filter(files, pattern):
                yield os.path.join(root, f)
    
    words = set()
    for file_name in find_files(".", "*.txt"):
        with open(file_name) as f:
            data = f.read()
        words.update(re.findall("\w+", data.lower()))
    

    还有几个cmets:

    1. 我通常期望字典在开始时会迅速增长;在这个过程的后期应该会发现很少的新单词,因此您的推断可能会严重高估单词列表的最终大小。

    2. 为此目的,集合非常有效。它们以哈希表的形式实现,添加一个新词的平均复杂度为 O(1)。

    【讨论】:

    • 感谢您的代码和观察。尤其是 Sets 的复杂性非常有趣。
    【解决方案5】:

    将您的密钥散列到更小、更易于管理的代码空间中。将散列键值到包含具有该散列值的键的文件中。哈希表要小得多,单个密钥文件要小得多。

    【讨论】:

    • 您对此有哈希算法的建议吗?平均单词使用的空间少于 32 个字符的 MD5 哈希...
    • @malexmave:请注意,MD5 哈希值是 128 位,因此您可以将它们存储在 16 个字节中。它们通常表示为 32 字节的十六进制字符串。当然,这不会改变您的观察结果——它们不会帮助您。
    • @malexmav 我的道歉,我不清楚,我的意思是一个非唯一的哈希,大概只有足够的位来让你的字典缩小到合理的大小。多个单词会散列成重复的代码,因此存储在同一个文件中。
    • F83 和许多 Forth 实现使用 16 位散列来加快对符号表的字查找。这不是大小问题,您不存储哈希值。就像索引到电话簿中的 Ts 一样,但您的索引函数不是按字母顺序排列的,而是一个多项式。这样你的分组就平衡了。
    • 这可能是一种方法,虽然我最终必须打开每个字典文件,删除重复项,然后合并所有它们并可选地对它们进行排序,这会增加运行时间(虽然我我想我很难要求一个不增加运行时间同时保证没有 MemoryError 终止的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多