4

我正在尝试使用 imaplib 从 Python 中的特定地址获取所有自动回复电子邮件。几周以来一切正常,但现在每次我运行我的程序时,我的所有 RAM 都被消耗掉了(几 GB!),脚本最终被 OOM 杀手杀死。

这是我目前正在使用的代码:

M = imaplib.IMAP4_SSL('server')
M.LOGIN('user', 'pass')
M.SELECT()
date = (datetime.date.today() - datetime.timedelta(1)).strftime("%d-%b-%Y")
result, data = M.uid('search', None, '(SENTON %s HEADER FROM "auto@site.com" NOT SUBJECT "RE:")' % date)
...

我确信应该返回少于 100 封几千字节的电子邮件。这里可能是什么问题?或者有没有办法限制返回的电子邮件数量?谢谢!

4

1 回答 1

0

没有办法确定原因是什么,无法重现问题(当然也不能没有看到触发问题的完整程序,并且知道您正在使用的所有依赖项的版本)。

但是,这是我最好的猜测。几个版本的 Python 包含一个非常浪费内存的 imaplib 实现。该问题在 Windows 上尤为明显,但不仅限于该平台。

问题的核心是从套接字读取字符串时的分配方式,以及 imaplib 从套接字读取字符串的方式。

从套接字读取时,Python 首先分配一个足够大的缓冲区来处理应用程序请求的字节数。这听起来可能是合理的,也许是 16 kB。然后将数据读入该缓冲区,并调整缓冲区的大小适应实际读取的字节数。

此操作的效率取决于平台重新分配实施的质量。调整缓冲区的大小最终可能会将其移动到更合适的位置,其中较小的大小可以避免浪费大量内存。或者它可能只是将不再作为该区域的一部分分配的内存的尾部标记为可重用(它甚至可以在实践中重用它)。或者它最终可能会浪费技术上未分配的内存。

想象一下,如果您必须读取几十 kB 的数据,并且数据一次从网络到达几十个字节,那么该内存被浪费的累积效应。更糟糕的是,想象一下数据是否真的在涓涓细流,而您一次只能得到几个字节。或者,如果您正在阅读数百 kB 的非常“大”的响应。

浪费的内存量——由进程有效分配,但不能以任何有意义的方式使用——可能是巨大的。100 kB 的数据,一次读取 5 个字节需要 20480 个缓冲区。如果每个缓冲区开始时为 16 kB,但收缩失败,导致它们保持在 16 Kb,那么您已分配至少 320 MB 的内存来保存 100 kB 的数据。

某些版本的 imaplib 通过引入多层缓冲和复制加剧了这个问题。一个非常旧的版本(希望不是您实际使用的版本)甚至一次读取 1 个字节(在上述情况下会导致 1.6GB 的内存使用)。

当然,这个问题通常不会出现在 Linux 上,在这种情况下,重新分配器并不是那么糟糕。在以前的 Python 版本(在最近的 2.x 版本之前)的各个阶段,该错误已“修复”,因此我不希望这些天会出现它。这并不能解释为什么您的程序在以这种方式失败之前运行了一段时间。

但这是我最好的猜测。

于 2012-02-29T16:11:50.900 回答