Python中的内存分配分析(支持Numpy数组)

27 投票
5 回答
5693 浏览
提问于 2025-04-16 02:04

我有一个程序,里面有很多对象,其中很多是Numpy数组。我的程序运行得很慢,我想减少内存的使用,因为在我现在的系统上,程序根本无法完成。

我在寻找一个好的工具,可以让我查看各种对象消耗的内存量(我想要一个类似于cProfile的内存分析工具),这样我就知道哪里需要优化。

我听说Heapy还不错,但可惜Heapy不支持Numpy数组,而我程序的大部分内容都是Numpy数组。

5 个回答

1

自从numpy 1.7版本以来,有一种半自带的方式可以用来跟踪内存分配情况:

https://github.com/numpy/numpy/tree/master/tools/allocation_tracking

10

可以看看 memory profiler。这个工具可以逐行分析代码的内存使用情况,并且和 Ipython 兼容,使用起来非常简单:

In [1]: import numpy as np

In [2]: %memit np.zeros(1e7)
maximum of 3: 70.847656 MB per loop

更新

正如 @WickedGrey 提到的,当一个函数被调用多次时,似乎存在一个bug(查看GitHub问题追踪),我可以复现这个问题:

In [2]: for i in range(10):
   ...:     %memit np.zeros(1e7)
   ...:     
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.894531 MB per loop
maximum of 1: 70.902344 MB per loop
maximum of 1: 70.902344 MB per loop
maximum of 1: 70.902344 MB per loop
maximum of 1: 70.902344 MB per loop

不过我不太清楚这个问题会对结果产生多大影响(在我的例子中似乎影响不大,所以根据你的使用情况,这个工具可能还是有用的),也不知道这个问题什么时候会被修复。我在 GitHub 上问过这个问题。

12

如果你在调用很多不同的函数,但不确定内存交换问题出在哪里,可以试试使用 memory_profiler 这个新功能。首先,你需要在你使用的不同函数上加上 @profile 这个标记。为了简单起见,我会用 memory_profiler 自带的例子 examples/numpy_example.py,里面有两个函数: create_data()process_data()

要运行你的脚本,不是用普通的 Python 解释器来运行,而是用 mprof 这个可执行文件,也就是:

$ mprof run examples/numpy_example.py

这样会生成一个名为 mprofile_??????????.dat 的文件,其中的 ? 会是当前日期的数字。要绘制结果,只需输入 mprof plot,它会生成一个类似于下面的图(如果你有多个 .dat 文件,它会总是选择最新的那个):

output of memory_profiler's mprof

在这里,你可以看到内存的使用情况,括号表示你进入或离开当前函数的时间。这样就很容易看出 process_data() 这个函数的内存使用达到了峰值。为了更深入地了解你的函数,你可以使用逐行分析工具,查看函数中每一行的内存使用情况。这个可以通过以下命令运行:

python -m memory_profiler examples/nump_example.py

这会给你类似于下面的输出:

Line #    Mem usage    Increment   Line Contents
================================================
    13                             @profile
    14  223.414 MiB    0.000 MiB   def process_data(data):
    15  414.531 MiB  191.117 MiB       data = np.concatenate(data)
    16  614.621 MiB  200.090 MiB       detrended = scipy.signal.detrend(data, axis=0)
    17  614.621 MiB    0.000 MiB       return detrended

从中可以清楚地看到,scipy.signal.detrend 正在分配大量的内存。

撰写回答