Утечка памяти при присвоении numpy .argpartition () элементу списка несколько раз - PullRequest
1 голос
/ 06 февраля 2020

У меня проблемы с пониманием утечки памяти в моем коде. Я полагаю, что моя ошибка связана с изменчивостью numpy массивов, поскольку ее можно решить с помощью .copy().

Я не понимаю, почему это происходит. Вот минимальный пример кода с утечкой памяти, который использует около 1600 МБ в памяти:

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 МБ памяти, как я и ожидал.

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() создает новый объект, и поэтому я не понимаю, почему copy() решает проблему с памятью. Даже если это не так, и np.argpartition() каким-то образом изменяет сам объект data, почему это приводит к утечке памяти?

1 Ответ

1 голос
/ 07 февраля 2020

Ваша проблема может быть сведена к следующему примеру:

import numpy as np

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

Здесь использование памяти объектом view также будет намного выше, чем использование памяти объектом copy.

Пояснение

Как описано в NumPy руководстве , «NumPy срез создает представление вместо копии». Поэтому основная память исходного массива «не будет освобождена до тех пор, пока все производные от него массивы не будут собраны в мусор».

При нарезке большого массива в документах Numpy также предлагается использовать copy(): «Уход должен быть взят при извлечении небольшой части из большого массива ... в таких случаях рекомендуется явное копирование (). "

Измерение использования памяти

Причина, по которой sys.getsizeof вернула одно и то же значение в обоих ваших примерах, заключается в том, что "только потребление памяти Приписывается непосредственно объекту, а не потреблению памяти объектами, к которым он относится ». В ваших примерах вы вызывали sys.getsizeof для объекта списка, который поэтому возвращает размер списка и не учитывает размер массивов NumPy в нем.

Например, sys.getsizeof([None for _ in data]) также будет return 87624.

Использование памяти numpy массивов

Чтобы получить размер массива data, вы можете вызвать sys.getsizeof с помощью data в качестве аргумента:

sys.getsizeof(data)

Теперь, чтобы получить размер всех массивов в вашем списке closest_neighbours, вы можете попробовать что-то вроде этого:

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

Имейте в виду, что это не будет работать, если в списке есть views. Как указано в Python Docs , sys.getsize "будет возвращать правильные результаты [для встроенных объектов], но это не должно выполняться для сторонних расширений, так как это определяется реализацией c «. А в случае NumPy просмотров view.__sizeof__() вернется 96.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...