从Python中释放内存回到操作系统的方法?

5

我有一段类似于这样的代码:

def memoryIntensiveFunction(x):
    largeTempVariable = Intermediate(x)
    processFunction(largeTempVariable,x)

问题在于变量temp在我的一个测试用例中大约有500 mb,但是当memoryIntensiveFunction完成后,该空间并未返回给操作系统。我知道这是因为使用guppy工具进行内存分析时,largeTempVariable被释放了(即在Python中),但是psutil显示它并没有被释放。我猜测我看到的是这里描述的影响。问题在于这个过程需要长时间运行(即数小时),memoryIntensiveFunction在开始时运行一次,之后不再运行,所以对我来说,必须要在数小时内携带这500mb的数据非常不便。
我在这里这里找到了一个解决方案,建议使用一个单独的进程。多进程会产生自己的成本,但对我来说这是值得的。然而,这将需要重构memoryIntensiveFunction的调用者,使其接收x作为返回值,而不是直接修改它。真正的问题在于我的对象x无法被picklable(它大量使用boost python扩展)。让x可被picklable需要付出很多工作。
是否有其他我没有考虑到的选项?
1个回答

2

这个问题看起来很奇怪,我尝试重现了一下,发现简单的“del”就足够了。为了演示,您可以运行以下代码:

import itertools
import pdb

def test():
    a = "a"
    for _ in itertools.repeat(None, 30):
        a += a
    pdb.set_trace()
    del a
    pdb.set_trace()

test()

首次断点处,您将看到它使用了大约1GB的RAM(您需要Python3.3入口):

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
 88.0 KiB + 103.0 KiB = 191.0 KiB       syslog-ng (2)
152.0 KiB +  55.0 KiB = 207.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
580.0 KiB + 220.5 KiB = 800.5 KiB       sshd (3)
768.0 KiB + 932.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 118.0 KiB =   2.9 MiB       mongod
  7.4 MiB + 109.0 KiB =   7.5 MiB       tmux [deleted] (2)
  1.0 GiB +   1.2 MiB =   1.0 GiB       python3.3
---------------------------------
                          1.0 GiB
=================================

然后在第二个断点处,当我们删除变量时,内存被释放:

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
 88.0 KiB + 103.0 KiB = 191.0 KiB       syslog-ng (2)
152.0 KiB +  55.0 KiB = 207.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
584.0 KiB + 220.5 KiB = 804.5 KiB       sshd (3)
768.0 KiB + 928.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 118.0 KiB =   2.9 MiB       mongod
  5.1 MiB +   1.2 MiB =   6.3 MiB       python3.3
  7.4 MiB + 109.0 KiB =   7.5 MiB       tmux [deleted] (2)
---------------------------------
                         20.3 MiB
=================================

现在,如果我们从函数中删除“del”,并在test()后设置断点:
import itertools
import pdb

def test():
    a = "a"
    for _ in itertools.repeat(None, 30):
        a += a
    pdb.set_trace()

test()
pdb.set_trace()

在终止之前,内存确实不会被释放:

 Private  +   Shared  =  RAM used       Program

  4.0 KiB +   9.0 KiB =  13.0 KiB       VisualGDB-DisownTTY-r1
  4.0 KiB +  15.0 KiB =  19.0 KiB       sharing-tests
  4.0 KiB +  19.5 KiB =  23.5 KiB       dhcpcd
  4.0 KiB +  31.5 KiB =  35.5 KiB       gdb
  4.0 KiB +  36.0 KiB =  40.0 KiB       vim [deleted]
  4.0 KiB +  38.0 KiB =  42.0 KiB       systemd-udevd
 40.0 KiB +  10.0 KiB =  50.0 KiB       init
 24.0 KiB + 135.0 KiB = 159.0 KiB       agetty (6)
 12.0 KiB + 150.0 KiB = 162.0 KiB       su (3)
160.0 KiB +  53.0 KiB = 213.0 KiB       crond
172.0 KiB +  81.0 KiB = 253.0 KiB       python3.4
628.0 KiB + 219.5 KiB = 847.5 KiB       sshd (3)
836.0 KiB + 152.0 KiB = 988.0 KiB       syslog-ng (2)
752.0 KiB + 957.0 KiB =   1.7 MiB       bash (13)
  2.8 MiB + 113.0 KiB =   2.9 MiB       mongod
  7.4 MiB + 108.0 KiB =   7.6 MiB       tmux [deleted] (2)
  1.0 GiB +   1.1 MiB =   1.0 GiB       python3.3
---------------------------------
                          1.0 GiB
=================================

所以我的建议是?在使用完后,只需删除这个东西,如果不再需要它的话;)

注意,我正在使用Python 2.7,因此在Python 3中可能有内存优化改进。此外,我能够获得一个涉及创建[1.0]*10**7的简单测试用例,并且使用psutil观察到del可以释放它,但是我的对象largeTempVariable在其数据结构上非常复杂,超出范围并不能将其释放回操作系统。 - amos
@amos 那它到底是什么?在 OP 中,你说它是一个大变量 - 这就是我用来测试的,而且在 2.7 中也是这样工作的。但现在你似乎表明它不是一个变量,而是一个列表?那它到底是什么? - Tymoteusz Paul
它是一个包含字典、集合、嵌套字典和集合等的对象。底部是整数和字符串... - amos
@amos,你的描述还是太过模糊了,无法准确找出导致内存无法释放的问题。鉴于此,我只能建议你重新设计类使其更简单,或者重构应用程序以更加原子化,这样你就可以将其拆分成单独的线程。 - Tymoteusz Paul
这可能是特定于操作系统的。我相信Linux特别容易不回收未使用的内存,因为未使用的内存只在纸上“使用”,也就是说,由于它不是“热”内存,将其交换出去本身就是一个足够的解决方案。 - Andrew Gorcester

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