【问题标题】:Identifying memory leak in python - Memory Profiler识别 python 中的内存泄漏 - Memory Profiler
【发布时间】:2016-02-19 17:28:48
【问题描述】:

我是 Python 新手,目前正在努力解决问题以提高我的编码技能。目前我正在研究一个问题,我必须stable sort 输入并输出反向稳定的排序值。我已经编程并在网站的在线判断中执行了代码,对于一个测试用例(不知道测试用例),我得到了Memory Limit Exceeded 错误。所以经过一番研究,我了解到代码中发生了memory leak,并且代码效率不高。所以我安装了python的memory_profiler来监控进程的内存消耗以及对我的代码的内存消耗的逐行分析。

请在下面找到来自memory_profiler 的输入详细信息、代码、输出和内存分析分析。

输入:

8
1 2
16 3
11 2
20 3
3 5
26 4
7 1
22 4 

代码:

from collections import OrderedDict
@profile
def test_1():
    print "Enter the number: "
    n = raw_input()
    k = []
    v = []
    print "Enter ID and M: "
    for i in range(0,int(n)):
        a, b = raw_input().split(' ')
        k.append(a)
        v.append(b)
    d = OrderedDict(zip(k,v))
    sorted_items = sorted(d.items(), key=lambda (k,v):int(v), reverse=True)
    for i, j in sorted_items:
        print i, j

if __name__ == '__main__':
        test_1()

输出:

    Line #    Mem usage    Increment   Line Contents
================================================
     2   10.520 MiB    0.000 MiB   @profile
     3                             def test_1():
     4   10.531 MiB    0.012 MiB        print "Enter the number: "
     5   10.551 MiB    0.020 MiB        n = raw_input()
     6   10.551 MiB    0.000 MiB        k = []
     7   10.551 MiB    0.000 MiB        v = []
     8   10.551 MiB    0.000 MiB        print "Enter ID and M: "
     9   10.551 MiB    0.000 MiB        for i in range(0,int(n)):
    10   10.551 MiB    0.000 MiB                a, b = raw_input().split(' ')
    11   10.551 MiB    0.000 MiB                k.append(a)
    12   10.551 MiB    0.000 MiB                v.append(b)
    13
    14   10.551 MiB    0.000 MiB        d = OrderedDict(zip(k,v))
    15   10.555 MiB    0.004 MiB        sorted_items = sorted(d.items(), key=lambda (k,v):int(v), reverse=True)
    16   10.555 MiB    0.000 MiB        for i, j in sorted_items:
    17   10.555 MiB    0.000 MiB                print i, j

预期输出(我能够得到想要的输出):

3 5
26 4
22 4
16 3
20 3
1 2
11 2
7 1

此代码对于更高的输入或更高的数字是否无效?从分析中我可以看到只有更少的内存使用,但对于那个特定的测试用例,我可以看到内存使用超过 16MB。 有人可以告诉我我在哪里做错了。我的方法是错误的还是流程是错误的?您能否告诉我为什么我无法按预期获得输出。提前致谢。任何帮助将不胜感激。

【问题讨论】:

  • 预期输出是什么?为什么你认为它应该使用更少的内存?
  • 更新了问题以显示预期的输出
  • 看起来您的代码是正确的,我的修订版也是如此。既然没有办法知道测试用例(设计这个在线课程的人真的应该重新考虑这一点),让我们看看是否会刮名单并将range更改为xrange会有帮助。
  • 当然,我会尝试使用xrange 执行代码,并在结果中查看它给出的执行时间和内存。
  • 不幸的是,这次也超出了内存限制 - Memory limit exceeded 0.374 16628 KB :(

标签: python memory memory-management memory-leaks


【解决方案1】:

我正在写评论,但是太长了,所以我想我不妨将其升级为答案。

首先,profile 装饰器本身就使用了相当多的内存。如您所见:

from memory_profiler import profile
@profile
def foo():
    pass

我明白了

Line #    Mem usage    Increment   Line Contents
================================================
    2     28.5 MiB      0.0 MiB   @profile
    3                             def foo():
    4     28.5 MiB      0.0 MiB       pass

您的数字可能会有所不同(我在 IDE 中运行 Python 3),但与您的函数基本相同。几乎所有内存使用都来自 @profile 行 (10.520 MiB),而您的函数添加的内容(参见增量列)是微不足道的 (0.36 MiB)。

结果是,从外观上看,您不应该有任何问题(如果您发布的已经是您的整个代码,我想是这样的话)。我不知道什么测试用例可以给你Memory Limit Exceeded。我们确实需要知道该测试用例是什么来调查问题。

也就是说,一项改进可以让您的代码更加高效,尤其是对于大量输入。您不需要构建中间列表(代码中的kv)。直接写入dict:

d = OrderedDict()
for i in range(int(n)):  # Note you don't need range(0, x); just range(x)
    a, b = raw_input().split()  # No need for an argument to split, either
    d[a] = b

更好的是,您可以避免for 循环并使用更高效的生成器表达式:

d = OrderedDict(raw_input().split() for _ in range(int(n)))

生成器表达式是(foo something_like_a_for_loop) 形式的表达式(正式描述here);如果它是函数的唯一参数,则可以省略周围的括号。它在很多方面就像一个列表:您可以使用for 对其进行迭代,您可以使用list 从中获取一个列表,您可以在需要迭代器时使用它。但是当列表很长时,它比等效列表占用的空间要少得多。 (但也有区别:gen expr 只能迭代一次,不能被索引并且没有len 等。你可以阅读更多关于它的信息here。 )

您还可以进行其他一些小的改进。全部合并到下面的重写中

def test_1():
    n = int(raw_input('Enter the number: '))
    d = OrderedDict(raw_input().split() for _ in range(n))
    sorted_items = sorted(d.items(), key=lambda k_v: int(k_v[1]), reverse=True)
    for i, j in sorted_items:
        print i, j

【讨论】:

  • 感谢您向我解释并帮助我提供更有效的解决方案。我会理解并学习它。我真的不知道那个测试用例是什么,因为它是一个在线法官,用他们自己的测试用例检查代码。所以几乎不可能得到那个特定的测试用例。但是让我尝试一下这个解决方案,看看它会导致什么。
  • 这就是我提供的完整解决方案
  • @Dev 使用 python 2 后的另一件事:使用 xrange 而不是 rangerange 一次创建整个列表并且可能很大; xrange(在 python 3 中变为 range)一次返回一个值并占用可忽略不计的内存:docs.python.org/2/library/functions.html#xrange
  • 谢谢。会这样做。我有几个疑问。你能澄清一下吗?首先 - d = OrderedDict(raw_input().split() for _ in range(n)) 这个函数到底发生了什么。我更像是一步一步的作家,但这非常精确,但我无法得到它。第二个 - key=lambda k_v: int(k_v[1]) _ 在这里做什么?
  • @Dev k_v 只是一个普通的标识符。我使用_ 来提醒自己它的值是一个 2 元组。它没有其他意义。你的 lambda (k, v) 实际上在 python 2 中非常好,但它是 not allowed in python 3,所以最好让自己习惯使用它。
猜你喜欢
  • 2013-02-05
  • 2012-12-24
  • 2013-03-19
  • 1970-01-01
  • 2011-03-21
  • 2011-03-28
  • 2015-02-11
  • 2019-08-19
相关资源
最近更新 更多