对于小型/大型 numpy 数组,释放的处理方式是不是不同?
Posted
技术标签:
【中文标题】对于小型/大型 numpy 数组,释放的处理方式是不是不同?【英文标题】:Is freeing handled differently for small/large numpy arrays?对于小型/大型 numpy 数组,释放的处理方式是否不同? 【发布时间】:2013-08-21 01:33:35 【问题描述】:我正在尝试调试我的大型 Python 应用程序的内存问题。大部分内存位于由 Python 类管理的 numpy
数组中,因此 Heapy 等是无用的,因为它们不考虑 numpy
数组中的内存。因此,我尝试使用 MacOSX (10.7.5) 活动监视器(或top
,如果您愿意)手动跟踪内存使用情况。我注意到以下奇怪的行为。在普通的python
解释器外壳(2.7.3)上:
import numpy as np # 1.7.1
# Activity Monitor: 12.8 MB
a = np.zeros((1000, 1000, 17)) # a "large" array
# 142.5 MB
del a
# 12.8 MB (so far so good, the array got freed)
a = np.zeros((1000, 1000, 16)) # a "small" array
# 134.9 MB
del a
# 134.9 MB (the system didn't get back the memory)
import gc
gc.collect()
# 134.9 MB
无论我做什么,Python 会话的内存占用都不会再低于 134.9 MB。所以我的问题是:
为什么大于 1000x1000x17x8 字节的数组资源(根据经验在我的系统上发现)正确地返回给系统,而较小数组的内存似乎永远被 Python 解释器卡住?
这似乎确实增加了,因为在我的实际应用程序中,我最终得到了超过 2 GB 的内存,我永远无法从 Python 解释器中取回。这是 Python 根据使用历史保留越来越多内存的预期行为吗?如果是,那么对于我的情况,Activity Monitor 和 Heapy 一样没用。有什么不是没用的吗?
【问题讨论】:
有趣的是,在 Linux 上,甚至更小的数组也会返回给操作系统。这非常令人惊讶,因为malloc
通常不会向操作系统返回任何内容——它只是将free
的内存放在自己的空闲列表中以供以后重用。
@larsmans:所以在 Linux 上创建/删除各种大小的 numpy 数组后,您没有看到 Python 解释器的内存使用量增加?
我看到它在np.zeros
之后增加,在del
之后再次减少。您是否尝试过malloc_history
或vmmap
之类的工具?这些可以让我们深入了解 Python/NumPy 如何处理内存。
@larsmans: ...在 Linux 上没有像我在 MacOSX 上看到的阈值大小(~130 MB)?因此,这似乎不是预期的行为。我会研究你建议的工具。
即使使用a = [np.zeros(10000) for i in xrange(10000)]
,我看到在del a
之后内存使用量又回到了旧水平。
【参考方案1】:
从Numpy's policy for releasing memory 读取,numpy
似乎没有 对内存分配/释放进行任何特殊处理。当引用计数变为零时,它只是调用free()
。事实上,用任何内置的 python 对象来复制这个问题是很容易的。问题出在操作系统级别。
Nathaniel Smith 在链接线程中的一个回复中解释了正在发生的事情:
一般来说,进程可以向操作系统请求内存,但它们不能 还给它。在C级,如果你调用
free()
,那么实际上是什么 发生的是您的进程中的内存管理库使 请注意,该内存未使用,并且可能从 未来malloc()
,但从操作系统的角度来看,它仍然是 “分配”。 (并且python在顶部使用了另一个类似的系统malloc()
/free()
,但这并没有真正改变任何东西。)所以操作系统 您看到的内存使用量通常是“高水位线”,最大 您的进程曾经需要的内存量。例外情况是对于大的单一分配(例如,如果您创建 一个多兆字节的数组),使用了不同的机制。这么大 内存分配可以释放回操作系统。所以它可能 特别是您的程序中正在生成的非
numpy
部分 你看到的问题。
所以,这个问题似乎没有通用的解决方案。分配许多小对象会导致工具描述的“高内存使用率”,即使它会在需要时被重用,而分配大对象不会释放后显示大量内存使用情况,因为内存已被操作系统回收。
你可以验证这个分配内置的python对象:
In [1]: a = [[0] * 100 for _ in range(1000000)]
In [2]: del a
在这段代码之后,我可以看到内存没有被回收,而这样做:
In [1]: a = [[0] * 10000 for _ in range(10000)]
In [2]: del a
内存被回收。
为避免内存问题,您应该分配大数组并使用它们(也许使用视图来“模拟”小数组?),或者尽量避免同时拥有许多小数组。如果您有一些创建小对象的循环,您可能会在每次迭代时显式释放不需要的对象,而不是仅在最后执行此操作。
我相信Python Memory Management 对 Python 中的内存管理方式提供了很好的见解。请注意,在“操作系统问题”之上,python 添加了另一层来管理内存领域,这可能会导致小对象的高内存使用率。
【讨论】:
这非常相关,谢谢。我可以使用l = [i for i in xrange(100000000)]
重现该行为,其中del l
没有立即回收内存。然而,在gc.collect()
之后,我找回了所有的记忆。有没有办法强制numpy
做同样的事情?
另外,如果这归结为操作系统内存使用指标对 Python/numpy 内存调试无用,并且由于 Heapy 等人不适用于 numpy 数组,那么是否存在可以用来调试大型 Python + numpy 项目的内存使用情况吗?
@Stefan 在整数的情况下可能是因为 整个 arena 被释放并且解释器决定释放它,并且可能 arenas大到足以触发“操作系统回收”行为。不幸的是,numpy
直接使用了malloc()
和free()
,这意味着python 解释器没有any 控制该内存;只有实现free()
的库才能控制它。不幸的是,我不知道有更好的工具可以分析这种情况。
@Stefan 在 linux 上,您的示例失败。在 python2 中,内存不会被回收(即使使用gc.collect()
),而在 python3 上,del l
足以回收内存。这种行为似乎在操作系统和 python 版本上都发生了变化(这是另一个线索,在某些情况下还涉及 python 如何管理他的内存领域。)以上是关于对于小型/大型 numpy 数组,释放的处理方式是不是不同?的主要内容,如果未能解决你的问题,请参考以下文章
scikit-learn joblib 错误:多处理池 self.value 超出“i”格式代码的范围,仅适用于大型 numpy 数组