17

我在 Python 中打开一个 3 GB 的文件来读取字符串。然后我将这些数据存储在字典中。我的下一个目标是使用这本字典构建一个图表,以便我密切监视内存使用情况。

在我看来,Python 将整个 3 GB 文件加载到内存中,我无法摆脱它。我的代码如下所示:

with open(filename) as data:

    accounts = dict()

    for line in data:
        username = line.split()[1]
        IP = line.split()[0]

        try:
            accounts[username].add(IP)
        except KeyError:
            accounts[username] = set()
            accounts[username].add(IP)

print "The accounts will be deleted from memory in 5 seconds"
time.sleep(5)
accounts.clear()

print "The accounts have been deleted from memory"
time.sleep(5)

print "End of script"

最后一行在那里,以便我可以监控内存使用情况。该脚本使用了超过 3 GB 的内存。清除字典可以释放大约 300 MB。当脚本结束时,剩余的内存被释放。

我正在使用 Ubuntu,并且我已经使用终端中的“系统监视器”和“免费”命令监控了内存使用情况。

我不明白的是为什么在我清除字典后 Python 需要这么多内存。文件是否仍存储在内存中?如果是这样,我该如何摆脱它?我的操作系统没有看到释放的内存有问题吗?

编辑:我试图在清除字典后强制执行 gc.collect(),但无济于事。

EDIT2:我在 Ubuntu 12.04.LTS 上运行 Python 2.7.3

EDIT3:我意识到我忘了提到一些非常重要的事情。我真正的问题不是我的操作系统没有“取回”Python 使用的内存。后来,Python 似乎没有重用该内存(它只是要求操作系统提供更多内存)。

4

4 回答 4

17

这对我来说也没有任何意义,我想弄清楚这是如何/为什么会发生的。(我认为这也应该是这样工作的!)我在我的机器上复制了它——尽管文件更小。

我在这里看到了两个离散的问题

  1. 为什么 Python 将文件读入内存(使用惰性行读取,它不应该 - 对吗?)
  2. 为什么 Python 不为系统释放内存

我对 Python 内部知识一无所知,所以我只是做了很多网络搜索。所有这一切都可能完全不合时宜。(我几乎不再发展了,过去几年一直在科技的商业方面工作)

懒人读...

我环顾四周,发现了这个帖子 -

http://www.peterbe.com/plog/blogitem-040312-1

它来自更早版本的python,但这条线引起了我的共鸣:

readlines() 一次读取整个文件并按行拆分。

然后我看到了这个,也是旧的 effbot 帖子:

http://effbot.org/zone/readline-performance.htm

关键的收获是:

例如,如果您有足够的内存,您可以使用 readlines 方法将整个文件放入内存中。

和这个:

在 Python 2.2 及更高版本中,您可以遍历文件对象本身。这很像幕后的 readlines(N),但看起来好多了

查看 xreadlines 的 pythons 文档 [ http://docs.python.org/library/stdtypes.html?highlight=readline#file.xreadlines ]:

此方法返回与 iter(f) 相同的内容 自 2.3 版以来不推荐使用:改为用于文件中的行。

这让我觉得也许正在发生一些啜饮。

因此,如果我们查看 readlines [ http://docs.python.org/library/stdtypes.html?highlight=readline#file.readlines ] ...

使用 readline() 读取直到 EOF 并返回包含如此读取的行的列表。

这似乎就是这里发生的事情。

然而, readline 看起来像我们想要的 [ http://docs.python.org/library/stdtypes.html?highlight=readline#file.readline ]

从文件中读取一整行

所以我尝试将其切换到 readline,并且该进程从未超过 40MB(之前它增长到 200MB,即日志文件的大小)

accounts = dict()
data= open(filename)
for line in data.readline():
    info = line.split("LOG:")
    if len(info) == 2 :
        ( a , b ) = info
        try:
            accounts[a].add(True)
        except KeyError:
            accounts[a] = set()
            accounts[a].add(True)

我的猜测是,我们并没有真正懒惰地阅读带有for x in data结构的文件——尽管所有文档和 stackoverflow 评论都表明我们是。 readline()对我来说消耗的内存明显减少,并且realdlines消耗的内存量与for line in data

释放内存

在释放内存方面,我对 Python 的内部结构不太熟悉,但我回想起我使用 mod_perl 的时候......如果我打开一个 500MB 的文件,那个 apache 子进程就会增长到那个大小。如果我释放了内存,它只会在那个孩子中是免费的——垃圾收集的内存在进程退出之前永远不会返回给操作系统。

所以我在这个想法上四处寻找,发现一些链接表明这可能会发生:

http://effbot.org/pyfaq/why-doesnt-python-release-the-memory-when-i-delete-a-large-object.htm

如果创建一个大对象并再次删除它,Python 可能已经释放了内存,但所涉及的内存分配器并不一定会将内存返回给操作系统,因此看起来 Python 进程使用了​​更多的虚拟内存比它实际使用的。

那有点老了,后来我在 python 中发现了一堆随机(已接受)补丁,这表明行为已更改,您现在可以将内存返回给操作系统(截至 2005 年,大多数补丁已提交并显然获得批准)。

然后我发现这个帖子http://objectmix.com/python/17293-python-memory-handling.html - 并注意评论#4

free()"""- 补丁 #1123430:Python 的小对象分配器现在会在一个 arena 中的所有内存再次未使用时返回一个 arena 给系统。在 Python 2.5 之前,arenas(256KB 内存块)从未被释放。一些应用程序会看到现在虚拟内存大小下降,尤其是长时间运行的应用程序,有时会临时使用大量小对象。请注意,当 Python 将竞技场返回到平台 Cfree()时,不能保证平台 C 库将反过来将该内存返回给操作系统。补丁的效果是停止使这成为不可能,并且在测试中它似乎至少在基于 Microsoft C 和 gcc 的系统上有效。感谢 Evan Jones 的辛勤工作和耐心.

因此,对于 linux 下的 2.4(如您所测试的那样),您确实不会总是取回已使用的内存,因为要收集大量的小对象。

因此(我认为)您在执行 f.read() 和 f.readlines() 之间看到的区别在于前者将整个文件作为一个大字符串对象(即不是一个小对象)读取,而后者则返回行列表,其中每一行都是一个 python 对象。

如果 'for line in data:' 构造本质上是 wrappingreadlines而不是readline,也许这与它有关?也许拥有单个 3GB 对象不是问题,而是拥有数百万个 30k 对象。

于 2012-09-14T00:02:33.230 回答
4

您正在尝试哪个版本的python?

我在 Python 2.7/Win7 上做了一个测试,它按预期工作,内存被释放。

在这里,我生成像您这样的示例数据:

import random

fn = random.randint

with open('ips.txt', 'w') as f: 
    for i in xrange(9000000):
        f.write('{0}.{1}.{2}.{3} username-{4}\n'.format(
            fn(0,255),
            fn(0,255),
            fn(0,255),
            fn(0,255),
            fn(0, 9000000),
        ))

然后是你的脚本。我用 defaultdict 替换了dict因为抛出异常会使代码变慢:

import time
from collections import defaultdict

def read_file(filename):
    with open(filename) as data:

        accounts = defaultdict(set)

        for line in data:
            IP, username = line.split()[:2]
            accounts[username].add(IP)

    print "The accounts will be deleted from memory in 5 seconds"
    time.sleep(5)
    accounts.clear()

    print "The accounts have been deleted from memory"
    time.sleep(5)

    print "End of script"

if __name__ == '__main__':
    read_file('ips.txt')

如您所见,内存达到了 1.4G,然后被释放,剩下 36MB:

defaultdict 的内存使用情况

使用您的原始脚本,我得到了相同的结果,但速度有点慢:

在此处输入图像描述

于 2012-09-14T02:51:03.927 回答
1

Python 何时释放内存以供Python重用和何时将内存释放回操作系统之间存在差异。Python 有用于某些类型对象的内部池,它会自行重用这些对象,但不会将其返回给操作系统。

于 2012-09-14T02:04:46.227 回答
0

gc 模块可能很有用,尤其是函数collect。我自己从未使用过它,但从文档来看,它看起来可能很有用。我会gc.collect()在你跑步之前尝试跑步accounts.clear()

于 2012-09-13T22:43:29.563 回答