多次将 numpy.argpartition() 分配给列表元素时发生内存泄漏

Memory leak when assigning numpy.argpartition() to list element multiple times

我无法理解代码中的内存泄漏。我想我的错误与可变的 numpy 数组有关,因为它可以使用 .copy().

来解决

不过我不明白为什么会这样。这是内存泄漏代码的最小示例,它使用了大约 1600MB 的内存:

import numpy as np
import sys

k_neighbours = 5
np.random.seed(42)
data = np.random.rand(10000)

for _ in range(3):
    closest_neighbours = [
        # get indices of k closest neighbours
        np.argpartition(
            np.abs(data-point),
            k_neighbours
        )[:k_neighbours]
        for point in data
    ]

print('\nsize:',sys.getsizeof(closest_neighbours))
print('first 3 entries:',closest_neighbours[:3])

这里是相同的代码,但添加了 .copy()。这似乎解决了问题,正如我所料,该程序的内存约为 80 MB。

for _ in range(3):
    closest_neighbours = [
        # get indices of k closest neighbours
        np.argpartition(
            np.abs(data-point),
            k_neighbours
        )[:k_neighbours].copy()
        for point in data
    ]

print('\nsize:',sys.getsizeof(closest_neighbours))
print('first 3 entries:',closest_neighbours[:3])

两者的最终结果相同:

size: 87624
first 3 entries: [
    array([   0, 3612, 2390,  348, 3976]),
    array([   1, 6326, 2638, 9978,  412]),
    array([5823, 5866,    2, 1003, 9307])
]

符合预期。

我还以为np.argpartition()创建了一个new对象,所以,我不明白为什么copy()解决了内存问题。即使情况并非如此并且 np.argpartition() 以某种方式更改了 data 对象本身,为什么会导致内存泄漏?

你的问题可以归结为这个例子:

import numpy as np

array = np.empty(10000)
view = array[:5]
copy = array[:5].copy()

这里view对象的内存占用也会比copy对象的内存占用高很多

说明

NumPy manual、"NumPy slicing creates a view instead of a copy"中所述。因此底层内存为原数组"will not be released until all arrays derived from it are garbage-collected."

在对大型数组进行切片时,Numpy 文档还建议使用 copy(): "Care must be taken when extracting a small portion from a large array ... in such cases an explicit copy() is recommended."

正在测量内存使用情况

在您的两个示例中 sys.getsizeof return 编辑相同值的原因是,"only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to." 在您的示例中,您在列表对象上调用了 sys.getsizeof,这因此 return 是列表的大小,不考虑其中的 NumPy 数组的大小。

例如,sys.getsizeof([None for _ in data]) 也会 return 87624

numpy数组的内存使用

要获得 data 数组的大小,您可以使用 data 作为参数调用 sys.getsizeof

sys.getsizeof(data)

现在,要获取 closest_neighbours 列表中所有数组的大小,您可以尝试这样的操作:

sum(sys.getsizeof(x) for x in closest_neighbours)

请注意,如果列表包含任何 views,这将不起作用。如 Python Docs 所述 sys.getsize "will return correct results [for built-in objects], but this does not have to hold true for third-party extensions as it is implementation specific." 而在 NumPy 视图的情况下 view.__sizeof__() 将 return 96.