Python переключает память при циклическом просмотре списка массивов - PullRequest
0 голосов
/ 13 января 2019

Я использую Python 3 и Numpy для анализа некоторых научных данных и столкнулся с проблемой памяти. При циклическом просмотре списка пустых массивов (несколько тысяч из них) и выполнении нескольких промежуточных вычислений я заметил, что python занимает на 6 ГБ больше памяти, чем я ожидал. Я выделил проблему в одну функцию, показанную ниже:

def overlap_correct(self):
    running_total = np.zeros((512, 512))
    shutter = 0
    for data_index in range(len(self.data)):
        if self.TOF[data_index] < self.shutter_times[shutter]:
            occupied_prob = running_total/self.N_TRIGS[data_index]
            running_total += self.data[data_index]
            self.data[data_index] = np.round(np.divide(self.data[data_index], (1 - occupied_prob)))
        else:
            running_total = np.zeros((512, 512))
            shutter += 1

Соответствующими структурами данных здесь являются self.data, представляющий собой список из пяти тысяч 512x512 числовых массивов, self.TOF и self.N_TRIGS - это числовые массивы из нескольких тысяч чисел с плавающей запятой, а self.shutter раз - это числовой массив с три плавания.

Во время обработки этого цикла, которая занимает несколько минут, я могу наблюдать постепенное увеличение использования памяти Python до тех пор, пока цикл не завершится с использованием примерно на 6 ГБ памяти больше, чем при его запуске.

Я использовал memory_profiler и objgraph для анализа использования памяти без какого-либо успеха. Мне известно, что до и после цикла self.data, self.TOF, self.N_TRIGS и self.shutter остаются одинакового размера и содержат одинаковое число и элементы одного типа. Если я правильно понимаю, локальные переменные, такие как occupied _prob, должны выходить из области видимости после каждой итерации цикла for, а если нет, то вся избыточная память должна собираться мусором после того, как функция вернется в основной цикл. Этого не происходит, и 6 ГБ остаются заблокированными до завершения сценария. Я также попытался запустить сборку мусора вручную, используя gc.collect() безрезультатно.

Если это помогает, эта функция существует внутри потока и является частью более крупного процесса анализа данных. Другие потоки не пытаются одновременно получить доступ к данным, и после завершения потока self.data копируется в другой класс. Экземпляр потока затем уничтожается при выходе из области видимости. Я также попытался вручную уничтожить поток, используя del thread_instance и thread_instance = None, но 6 ГБ остаются заблокированными. Это не является большой проблемой на компьютере разработчика, но код будет частью более крупного пакета, который может работать на машинах с ограниченным объемом оперативной памяти.

1 Ответ

0 голосов
/ 13 января 2019

Мне удалось найти решение проблемы. TL; DR: во время выполнения функции dtype из self.data не применялось.

Первая проблема, которая помешала мне осознать это, заключается в том, что с помощью sys.getsizeof(), чтобы увидеть, сколько места заняло self.data в памяти, я получил размер списка указателей на numpy.ndarray объектов, что остался прежним, так как количество массивов не изменилось.

Во-вторых, когда я проверял dtype из self.data[0], который был единственным неизменным «слайдом» данных, я ошибочно предположил, что весь список массивов также имеет тот же dtype.

Я подозреваю, что причина, по которой dtype некоторых массивов был изменен, заключается в том, что np.round() возвращает округленное float.

Изменив структуру self.data из списка из нескольких тысяч массивов 256x256 в трехмерный массив [a few thousand]x[256]x[256], функция больше не угадывала dtype данных, но молча произвела возврат float64 на np.round до uint16:

self.data = np.asarray(self.data, dtype='uint16')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...