Python内存错误(有足够可用的内存)

3
我正在尝试计算文本文件中字符串出现的次数。 文本文件看起来像这样,每个文件约为200MB。
String1 30
String2 100
String3 23
String1 5
.....

我想将计数保存到字典中。
count  = {}
for filename in os.listdir(path):
    if(filename.endswith("idx")):
        continue
    print filename  
    f = open(os.path.join(path, filename))
    for line in f:
        (s, cnt) = line[:-1].split("\t")
        if(s not in count):
            try:
                count[s] = 0 
            except MemoryError:
                print(len(count))
                exit()
        count[s] += int(cnt)  
    f.close()
    print(len(count))

count[s] = 0处出现了内存错误, 但我的计算机仍有更多可用内存。
我该如何解决这个问题? 谢谢!

更新: 我在此处复制了实际代码。 我的 python 版本是 2.4.3,计算机运行的是 linux,并且有大约 48G 的内存,但只使用了不到 5G。代码停在len(count)=44739243

更新2: 字符串可以重复(不是唯一的字符串),因此我想将所有字符串的计数相加。我想要的操作只是读取每个字符串的计数。每个文件大约有 1000 万行,我有超过 30 个文件。我预计计数少于 1000 亿。

更新3: 操作系统为 linux 2.6.18。


getStringCount 长什么样? - sean
4
我猜想 f.close() 上面的三行缩进有误,是吗? - Junuxx
抱歉,缩进有误。我已经修复了。我没有复制粘贴原始代码,我对其进行了简化。 - user987654
1
在关闭每个文件后,尝试使用print(len(count))print(sys.getsizeof(count)),以了解字典的大小。 - Junuxx
1
与内存无关:您可以使用collections.defaultdict(int)来简化您的代码。 - jfs
显示剩余5条评论
3个回答

4

cPython 2.4在x64上可能存在大内存分配问题:

$ python2.4 -c "'a' * (2**31-1)"
Traceback (most recent call last):
  File "<string>", line 1, in ?
MemoryError
$ python2.5 -c "'a' * (2**31-1)"
$

更新到最新的Python解释器(如cPython 2.7)以解决这些问题,并确保安装64位版本的解释器。

如果字符串长度较大(即超过您示例中的<10个字节),您可能还想仅存储其哈希值,或者甚至使用概率性(但更高效)的存储方式,如布隆过滤器。要存储它们的哈希值,请将文件处理循环替换为

import hashlib
# ...
for line in f:
    s, cnt = line[:-1].split("\t")
    idx = hashlib.md5(s).digest()
    count[idx] = count.get(idx, 0) + int(cnt)
# ...

MD5:不如安全哈希安全,也不如快速哈希快。为此,请使用Python内置的(快速但不安全)哈希。 - Michael Lorton

1

如果你只是想计算唯一字符串的数量,你可以通过对每个字符串进行哈希处理来大大减少内存占用:

    (s, cnt) = line[:-1].split("\t")
    s = hash(s)

1

我不太确定为什么会出现这个崩溃。你的字符串的平均长度是多少?如果有44百万个字符串,而且它们有点长,你可能应该考虑对它们进行哈希处理,就像已经建议的那样。缺点是你失去了列出唯一键的选项,你只能检查一个字符串是否在你的数据中。

关于内存限制已经达到5 GB,也许与你过时的Python版本有关。如果你有更新的选项,获取2.7版本。相同的语法(加上一些额外的内容),没有问题。好吧,我甚至不知道下面的代码是否还与2.4兼容,也许你必须再次删除with语句,至少这是你在2.7中编写它的方式。

与你的版本的主要区别是手动运行垃圾回收。此外,你可以提高Python使用的内存限制。正如你提到的,它只使用实际RAM的一小部分,所以如果有一些奇怪的默认设置禁止它变得更大,请尝试这样做:

MEMORY_MB_MAX = 30000
import gc
import os
import resource
from collections import defaultdict
resource.setrlimit(resource.RLIMIT_AS, (MEMORY_MB_MAX * 1048576L, -1L))

count  = defaultdict(int)
for filename in os.listdir(path):
    if(filename.endswith("idx")):
        continue
    print filename  
    with open(os.path.join(path, filename)) as f:
        for line in f:
            s, cnt = line[:-1].split("\t")
            count[s] += int(cnt)  
    print(len(count))
    gc.collect()

此外,我不明白你的代码行 s, cnt = line[:-1].split("\t") 的含义,尤其是 [:-1]。如果文件看起来像你所说的那样,那么这将清除数字的最后一个数字。这是有意为之吗?

谢谢您的回答!它可以在Python 2.7中使用。我使用“[:-1]”是为了删除“\n”。 - user987654
无关紧要。对行的迭代将删除它,即使它仍然存在,转换为int也会清除任何周围的空格。去掉它,字符串就不必为每一行复制。这应该是显而易见的,考虑到数据的总量。 - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接