Предположим, у меня есть большой массив numpy в памяти, у меня есть функция, func
которая принимает этот гигантский массив в качестве входных данных (вместе с некоторыми другими параметрами). func
с разными параметрами можно запускать параллельно. Например:
def func(arr, param):
# do stuff to arr, param
# build array arr
pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]
Если я использую многопроцессорную библиотеку, то этот гигантский массив будет многократно скопирован в разные процессы.
Есть ли способ разрешить различным процессам использовать один и тот же массив? Этот объект массива доступен только для чтения и никогда не будет изменен.
Что еще сложнее, если arr - это не массив, а произвольный объект Python, есть ли способ поделиться им?
[Изменено]
Я прочитал ответ, но все еще немного запутался. Поскольку fork () является копированием при записи, мы не должны вызывать каких-либо дополнительных затрат при создании новых процессов в библиотеке многопроцессорной обработки Python. Но следующий код предполагает огромные накладные расходы:
from multiprocessing import Pool, Manager
import numpy as np;
import time
def f(arr):
return len(arr)
t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;
pool = Pool(processes = 6)
t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;
вывод (и, кстати, стоимость увеличивается с увеличением размера массива, поэтому я подозреваю, что накладные расходы, связанные с копированием памяти, все еще существуют):
construct array = 0.0178790092468
multiprocessing overhead = 0.252444982529
Почему возникают такие огромные накладные расходы, если мы не копировали массив? А какую часть меня спасает разделяемая память?
Ответы:
Если вы используете операционную систему, которая использует
fork()
семантику копирования при записи (как и любой распространенный unix), то до тех пор, пока вы не измените структуру данных, она будет доступна для всех дочерних процессов, не занимая дополнительной памяти. Вам не нужно будет делать ничего особенного (за исключением того, что убедитесь, что вы не изменили объект).Наиболее эффективного , что вы можете сделать для вашей проблемы будет упаковать массив в эффективную структуру массива ( с использованием
numpy
илиarray
) место , которое в общей памяти, оберните егоmultiprocessing.Array
, и передать свои функции. Этот ответ показывает, как это сделать .Если вам нужен доступный для записи общий объект, вам нужно будет обернуть его какой-либо синхронизацией или блокировкой.
multiprocessing
предоставляет два метода для этого : один с использованием общей памяти (подходит для простых значений, массивов или ctypes) илиManager
прокси, где один процесс хранит память, а менеджер разрешает доступ к ней из других процессов (даже по сети).Этот
Manager
подход можно использовать с произвольными объектами Python, но он будет медленнее, чем эквивалент, использующий разделяемую память, поскольку объекты необходимо сериализовать / десериализовать и пересылать между процессами.В Python доступно множество библиотек и подходов для параллельной обработки .
multiprocessing
- отличная и хорошо продуманная библиотека, но если у вас есть особые потребности, возможно, лучше будет один из других подходов.источник
apply_async
должна ссылаться на общий объект в области видимости напрямую, а не через его аргументы.Я столкнулся с той же проблемой и написал небольшой служебный класс с общей памятью, чтобы обойти ее.
Я использую
multiprocessing.RawArray
(lockfree), а также доступ к массивам вообще не синхронизирован (lockfree), будьте осторожны, не стреляйте себе ногами.С этим решением я получаю ускорение примерно в 3 раза на четырехъядерном i7.
Вот код: не стесняйтесь использовать и улучшать его, и, пожалуйста, сообщайте о любых ошибках.
источник
Это предполагаемый вариант использования Ray , библиотеки для параллельного и распределенного Python. Под капотом он сериализует объекты, используя макет данных Apache Arrow (который является форматом с нулевым копированием), и сохраняет их в хранилище объектов с общей памятью, чтобы к ним могли обращаться несколько процессов без создания копий.
Код будет выглядеть следующим образом.
Если вы не вызываете,
ray.put
то массив все равно будет храниться в общей памяти, но это будет происходить один раз при вызовеfunc
, а это не то, что вам нужно.Обратите внимание, что это будет работать не только для массивов, но и для объектов, содержащих массивы. , например, словари, отображающие целые значения на массивы, как показано ниже.
Вы можете сравнить производительность сериализации в Ray и pickle, запустив следующее в IPython.
Сериализация с Ray лишь немного быстрее, чем pickle, но десериализация в 1000 раз быстрее из-за использования разделяемой памяти (это число, конечно, будет зависеть от объекта).
См. Документацию Ray . Вы можете узнать больше о быстрой сериализации с помощью Ray и Arrow . Обратите внимание, я один из разработчиков Ray.
источник
Как упоминал Роберт Нишихара, Apache Arrow упрощает эту задачу, особенно с помощью хранилища объектов в памяти Plasma, на котором построен Ray.
Я сделал мозговую плазму специально для этой цели - быстрой загрузки и перезагрузки больших объектов в приложении Flask. Это пространство имен объектов с общей памятью для сериализуемых объектов Apache Arrow, включая
pickle
строки байтов 'd, сгенерированные с помощьюpickle.dumps(...)
.Ключевое отличие Apache Ray и Plasma заключается в том, что они отслеживают идентификаторы объектов за вас. Любые процессы, потоки или программы, которые выполняются локально, могут совместно использовать значения переменных, вызывая имя из любого
Brain
объекта.источник