Есть ли известная проблема с утечками памяти в Python dicts или Numpy итераторах? - PullRequest
1 голос
/ 21 июня 2019

Функция, выполняющая итерации по большому массиву Numpy с помощью nd.iter, потребляет гораздо больше памяти, чем ожидалось.

У меня есть большое (~ 120 МБ) изображение, содержащее значения классов (каждое из значений uint8 между 0 и 9). Для каждого класса я хотел бы создать список признаков, которые можно использовать для дальнейших операций, например:

class_dict = {
    1:[(2,3),(2,4),(2,5).....],   # Pixels containing class 1
    2:[(30000,2333),(54444,23232) .....],    # Pixels containing class 2
    ....
}

В настоящее время это производится с помощью следующего кода:

class = gdal.Open("path/to/class/geotiff")
class_array = gdal.GetVirtualMemArray()  # shape=(11027,10954)

def build_class_dict(class_array, no_data=None):
    """Returns a dict of coordinates of the following shape:
    [class, coord_list]"""
    out_dict = {}
    it = np.nditer(class_array, flags=['multi_index'])
    while not it.finished:
        this_class = int(it.value)
        if this_class == no_data:
            it.iternext()
            continue
        if this_class in out_dict.keys():
            out_dict[this_class].append(it.multi_index)
        else:
            out_dict.update({this_class: [it.multi_index]})
        it.iternext()
    return out_dict

Учитывая, что я, по сути, дважды сохраняю все изображение, я ожидаю, что объем памяти этой функции вырастет совсем немного - скажем, до ~ 3 ГБ, учитывая обычные издержки Python, что не проблема.

Что происходит, так это то, что площадь программы растет очень быстро, заполняя все доступное пространство. Это завершается примерно за минуту для изображений с большим количеством (~ 80%) значений ноданных, но для чего-то еще это истощает машину ресурсов и блокирует.

Есть ли проблема с памятью в этой реализации, или мне придется переосмыслить? Окружение: Python 3.6.7, numpy 1.15.4 (но пробовал на 1.16.4), gdal 2.4.1, работает на ubuntu 18.02.

EDIT Дальнейшее развитие; при профилировании функции с помощью декоратора @ memory_profiler.profile память скачет, а затем останется неизменной - что я и ожидал. Это не то, как он вел себя, когда его не контролировали, но он также работает намного медленнее.

1 Ответ

0 голосов
/ 21 июня 2019

Таким образом, исходный массив имеет форму (11027,10954) и uint8 dtype.Его использование памяти составляет примерно 120789758 байт - один байт на пиксель.

На данный момент игнорируется nodata, словарь содержит одинаковое количество кортежей, каждый из которых содержит два целых числа (по 4 или 8 байт), разделенных между10 списков.Это 3 * 120789758 указателей.Гораздо выше использование памяти.Это не утечка, а просто структура данных, которая требует много памяти.

Обычно я не рекомендую использовать nditer в коде Python, поскольку он не дает никакого преимущества в скорости по сравнению с простой итерацией.Но поскольку вы используете его для мультииндексов, это, вероятно, неплохой выбор.

Использование вашего словаря очень похоже на collections.defaultdict.Это может сократить ваш код, но не изменит использование памяти или скорость.

Если nodata равно 0, вы также можете поместить это изображение в scipy.sparse.dok_matrix - за исключением того, что ключи словаря являются самими индексными кортежами,и словарь значений значений пикселей.

...