对于小型/大型 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_historyvmmap 之类的工具?这些可以让我们深入了解 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 数组,释放的处理方式是不是不同?的主要内容,如果未能解决你的问题,请参考以下文章

在多处理进程之间共享大型只读 Numpy 数组

NumPy 填充大型数组的给定边界框坐标内的值

scikit-learn joblib 错误:多处理池 self.value 超出“i”格式代码的范围,仅适用于大型 numpy 数组

如何在 python 中处理大型图像数据集?

具有大型数组的 Windows 上的 Python 多处理

numpy模块