Проблемы с производительностью памяти NumPy

У меня есть большой (75000 x 5 x 6000) трехмерный массив, хранящийся в виде карты памяти NumPy. Если я просто перебираю первое измерение следующим образом:

import numpy as np
import time

a = np.memmap(r"S:\bin\Preprocessed\mtb.dat", dtype='float32', mode='r', shape=(75000, 5, 6000))
l = []
start = time.time()
index = np.arange(75000)
np.random.shuffle(index)
for i in np.array(index):
    l.append(np.array(a[i]) * 0.7)
print(time.time() - start)

>>> 0.503

Итерация происходит очень быстро. Однако, когда я пытаюсь перебрать одну и ту же карту памяти в контексте более крупной программы, отдельные вызовы карты памяти будут занимать целых 0,1 секунды, а извлечение всех 75000 записей займет почти 10 минут.

Большая программа слишком длинная, чтобы воспроизводить ее здесь, поэтому мой вопрос: существуют ли какие-либо известные проблемы, которые могут привести к значительному замедлению доступа к memmap, возможно, если в памяти Python хранится значительный объем данных?

В более крупной программе использование выглядит так:

import time
array = np.memmap(self.path, dtype='float32', mode='r', shape=self.shape)
for i, (scenario_id, area) in enumerate(self.scenario_areas):
    address = scenario_matrix.lookup.get(scenario_id)
    if address:
        scenario_output = array[address]
        output_total = scenario_output * float(area)
        cumulative += output_total  # Add results to cumulative total
        contributions[int(scenario_id.split("cdl")[1])] = output_total[:2].sum()
del array

Второй пример выполняется более 10 минут. Время строки scenario_output = array[address], которая просто извлекает запись из карты памяти, варьируется от 0,0 до 0,5 — полсекунды для извлечения одной записи.


person triphook    schedule 17.03.2017    source источник
comment
вам нужно немедленно записать данные обратно в массив? r+ может вас тормозить..   -  person Aaron    schedule 17.03.2017
comment
Я предполагаю, что ваш файл составляет около 9 ГБ? (8,4 ГиБ) 10 минут с добавленной обработкой и обратной записью не так уж и ужасно (если вы не используете ssd)   -  person Aaron    schedule 17.03.2017
comment
Известная проблема заключается в том, что если все вещи не помещаются в память, вы будете обмениваться/выполнять io. Это может быть очень медленным, если ваш доступ не является последовательным в порядке хранения.   -  person pvg    schedule 17.03.2017
comment
Но почему в примере, который я привел, я могу перебрать весь блок за доли секунды? Чем отличается выполнение той же процедуры в контексте более крупной программы?   -  person triphook    schedule 17.03.2017
comment
Цикл вашего примера быстрый, потому что вы на самом деле ничего не делаете с данными, даже не читаете их. Конечно, выполнение реальной работы будет медленным. Это 9 гигабайт.   -  person user2357112 supports Monica    schedule 17.03.2017
comment
Разница в том, что вы предположительно получаете доступ ко всем своим данным. В вашем коротком примере вы получаете доступ к некоторым из них последовательно. Какова схема доступа в вашей более крупной программе?   -  person pvg    schedule 17.03.2017
comment
@pvg: пример даже не обращается к каким-либо данным, а просто получает просмотры! Новый взгляд на данные не требует их чтения.   -  person user2357112 supports Monica    schedule 17.03.2017
comment
Из того, что вы добавили, невозможно сказать, является ли доступ последовательным. Если доступ не является последовательным, это может стать очень медленным. Если у вас мало гигабайт данных (которые не обязательно помещаются в память, но даже когда помещаются), вам лучше обрабатывать их последовательно. Таким образом, если вы можете управлять своим циклом обработки из большого массива данных, а не искать в нем случайные вещи, производительность будет значительно выше.   -  person pvg    schedule 17.03.2017
comment
См. редактирование: я все еще могу сделать 75000 случайных выборок менее чем за 5 секунд вне более крупного сценария.   -  person triphook    schedule 17.03.2017
comment
Что такое address? Всегда ли это скаляр?   -  person user2357112 supports Monica    schedule 17.03.2017
comment
Да. script_matrix.lookup — это словарь, возвращающий целочисленный номер строки, соответствующий записи в карте памяти.   -  person triphook    schedule 17.03.2017
comment
И расположены ли последовательные значения address по порядку или разбросаны повсюду? время случайного поиска на диске может повредить скорости.   -  person hpaulj    schedule 17.03.2017
comment
Можете ли вы изменить внутренний цикл ваших тестовых примеров, чтобы он делал что-то вроде * 0,7, как ваша реальная программа? Кроме того, вы можете комбинировать все эти вещи, чтобы сделать ваш вопрос читабельным, нет необходимости добавлять историю редактирования вручную, она все равно сохраняется.   -  person pvg    schedule 18.03.2017
comment
Вы случайно не печатаете эти временные данные на консоль при каждом доступе, не так ли?   -  person pvg    schedule 20.03.2017
comment
Не в первом примере. Я нахожусь во втором только для того, чтобы продемонстрировать, что время на этой конкретной линии может достигать полсекунды. Если я уберу оператор печати, выполнение всего цикла все равно будет на несколько порядков выше, чем в первом примере.   -  person triphook    schedule 20.03.2017
comment
Хорошо, но какие порядки? Кроме того, на каком устройстве находится файл, сколько физической памяти на машине и сколько обращений на самом деле выполняет ваша более крупная программа?   -  person pvg    schedule 20.03.2017
comment
Вы можете использовать line_profiler и profile, чтобы узнать, где находятся узкие места, а затем попытаться их устранить. Повторяйте это, пока не будете удовлетворены.   -  person MSeifert    schedule 20.03.2017
comment
@pvg Извините, я удалил эту деталь, когда убирал правки. Второй сценарий занимает более 10 минут. Само извлечение scenario_output = array[address] раз обнуляется в первом примере и целых 0,5 секунды во втором, даже если выполняется такое же извлечение из того же массива.   -  person triphook    schedule 20.03.2017
comment
Файл находится на моем внутреннем жестком диске. Файл весит 16,1 Гб. Более крупный сценарий не делает никаких других обращений к этому конкретному файлу, хотя он извлекает данные из многих других отображаемых в память файлов.   -  person triphook    schedule 20.03.2017
comment
«хотя он хорошо извлекается из многих других файлов с отображением памяти». Это может быть вещью. Если вы заполняете память и файловый кеш другими вещами, вы, скорее всего, получите хиты от свопинга. Я предполагаю, что ваш внутренний диск - это жесткий диск. Каков фактический объем памяти вашего скрипта по сравнению с вашей общей памятью? И сколько обращений вы делаете к большому файлу, невозможно сказать из предоставленного вами кода.   -  person pvg    schedule 20.03.2017
comment
Процесс использует 2,5 ГБ из 32 ГБ доступной памяти. Под «доступом» вы имеете в виду, сколько раз он был открыт с помощью команды np.memmap или сколько раз он тянулся к открытому массиву? Первые десятки, вторые тысячи. Странно то, что иногда более крупный скрипт будет работать очень быстро (с той же скоростью, что и в первом примере), поэтому я предполагаю, что это как-то связано с памятью или другими накладными расходами, но все же кажется, что у меня много доступных ресурсов.   -  person triphook    schedule 20.03.2017
comment
Под доступом я подразумеваю, сколько раз вы обращаетесь к фактическим данным, а не сколько раз вы выполняете mmap. Тысячи это что, 2000? 70000 тысяч? Нет ничего странного в том, что скрипт иногда запускается быстро, потому что, по сути, он кэшируется в памяти. Он должен работать намного быстрее во второй раз, если вы запустите его дважды подряд.   -  person pvg    schedule 20.03.2017
comment
Одна вещь, которую стоит попробовать для получения более согласованных чисел, — это получить wj32.org/wp/software/empty. -standby-list, а затем запустить EmptyStandbyList.exe standbylist и затем измерить время, скажем, для вашего простого скрипта. В этом случае не должно быть 0,5 секунды.   -  person pvg    schedule 20.03.2017
comment
можно попробовать воспроизвести на линуксе? Реализация mmap в Windows отличается и иногда вызывает такие проблемы, как эта.   -  person Marat    schedule 24.03.2017


Ответы (2)


Насколько мне известно, в python нет ограничений, связанных с memmaps, которые не зависели бы от общих ограничений на уровне ОС. Так что я предполагаю, что у вас либо есть узкое место памяти на уровне ОС (возможно, взаимодействие между кэшированием разных больших mmap), либо ваша проблема где-то еще.

Очень хорошо, что у вас уже есть эталонная реализация, которая показывает, насколько быстрой должна быть операция. Вам нужно будет систематически проверять различные возможные причины. Вот несколько направлений, которые могут помочь определить причину.

Во-первых, используйте cProfile как для эталонной реализации, так и для лучшего понимания узкого места. Вы получите список вызовов функций и время, затраченное на каждую функцию. Это может привести к неожиданным результатам. Некоторые предположения:

  1. Правда ли, что большая часть времени проходит внутри фрагмента кода, который вы опубликовали? В противном случае профилирование может намекнуть на другое направление.
  2. Является ли self.scenario_areas списком или это итератор, который выполняет какие-то скрытые и дорогостоящие вычисления?
  3. Возможно, поиск scenario_matrix.lookup.get(scenario_id) идет медленно. Проверь это.
  4. Является ли contributions обычным списком Python или dict, или он делает что-то странное при назначении за кулисами?

Только если вы убедились, что время действительно потрачено на строку scenario_output = array[address], я бы начал выдвигать гипотезу о взаимодействии между файлами mmap. Если это так, начните комментировать части кода, которые связаны с другим доступом к памяти, и повторяйте профилирование кода, чтобы лучше понять, что происходит.

Надеюсь, это поможет.

person Peter    schedule 23.03.2017

Вы, вероятно, не сможете избежать проблем с производительностью, используя np.memmap,

Я предлагаю попробовать что-то вроде https://turi.com/products/create/docs/generated/graphlab.SFrame.html

SFrame/SArray позволяют читать табличные данные прямо с диска, что часто бывает быстрее для больших файлов данных.

Он имеет открытый исходный код и доступен по адресу https://github.com/turi-code/SFrame.

person Stanley Kirdey    schedule 26.03.2017