隐藏引用函数参数导致大量内存使用?

时间:2016-09-22 16:14:50

标签: python function memory

编辑:没关系,我只是完全愚蠢。

我遇到了在越来越小的子串上递归的代码,这里的重点是我的测试内容:

def f(s):
    if len(s) == 2**20:
        input('check your memory usage again')
    else:
        f(s[1:])
input('check your memory usage, then press enter')
f('a' * (2**20 + 500))

在调用之前,我的Python进程大约需要9 MB(由Windows任务管理器检查)。在具有~1MB字符串的500个级别之后,它在大约513MB处。毫不奇怪,因为每个调用级别仍然保持其s变量中的字符串。

但是我尝试通过替换对字符串的引用来修复它,并引用了新字符串,它仍然高达513 MB:

def f(s):
    if len(s) == 2**20:
        input('check your memory usage again')
    else:
        s = s[1:]
        f(s)
input('check your memory usage, then press enter')
f('a' * (2**20 + 500))

为什么不释放内存?字符串甚至只会变小,因此以后的字符串很容易适应早期字符串的空间。是否隐藏了对字符串的额外引用或正在发生的事情?

我曾预料到它的行为会像这样,最多只能达到10 MB(更改为1 MB,正如预期的那样,因为新字符串是在旧字符串仍然存在的情况下构建的):

input('check your memory usage, then press enter')
s = 'a' * (2**20 + 500)
while len(s) != 2**20:
    s = s[1:]
input('check your memory usage again')

(别担心时间的复杂性很差,顺便说一下,我知道,不要打扰。)

3 个答案:

答案 0 :(得分:2)

您的函数是递归的,因此当您调用f()时,您的当前帧将被放入堆栈,并创建一个新帧。所以基本上每个函数调用都会保持对它创建的新字符串的引用,以传递给下一个调用。

说明堆栈

import traceback

def recursive(x):
    if x:
        recursive(x[1:])
    else:
        traceback.print_stack()

recursive('abc')

给出

$ python tmp.py
  File "tmp.py", line 10, in <module>
    recursive('abc')
  File "tmp.py", line 5, in recursive
    recursive(x[1:])
  File "tmp.py", line 5, in recursive
    recursive(x[1:])
  File "tmp.py", line 5, in recursive
    recursive(x[1:])
  File "tmp.py", line 7, in recursive
    traceback.print_stack()

当对recursive()的最终通话返回时,它会返回上方的下一个通话,该通话仍然引用x

  

但是我尝试通过替换对字符串的引用来修复它,并引用了新字符串,它仍然达到了513 MB

你在调用的当前函数中做了什么,但是调用它的函数仍然引用了传入的函数。例如

def foo(x):
    print "foo1", locals()
    bar(x)
    print "foo2", locals()

def bar(x):
    print "bar1", locals()
    x = "something else"
    print "bar2", locals()

foo('original thing')

调用foo()时,会将字符串'original thing'传递给bar()。即使bar()然后删除了引用,上面foo()的当前调用仍然有引用

$ python tmp_x.py 
foo1 {'x': 'original thing'}
bar1 {'x': 'original thing'}
bar2 {'x': 'something else'}
foo2 {'x': 'original thing'}

我希望这说明一下。在我关于堆栈帧的第一个陈述中,我有点模糊。

答案 1 :(得分:1)

  

是否隐藏了对字符串的附加引用或正在进行的内容

好吧,每个函数都有一个对其字符串的引用,而它在栈中,所以s = s[1:]仍然会在下一个函数调用中保持s[1:]。在500次递归调用之后,每次复制1个字符的事实对于每次传递的大约2 ** 20个字符来说是无关紧要的。

答案 2 :(得分:0)

虽然每个通话级别 都会删除自己的旧字符串,但创建并且保持自己的字符串。

(在阅读其他答案之后,用我自己的话来说,更直接地解决我(问题作者)遗漏的问题。)