Подтвердить что ты не робот

Параллельный цикл python с массивами numpy и разделяемой памятью

Я знаю несколько вопросов и ответов по этой теме, но не нашел удовлетворительного ответа на эту конкретную проблему:

Каков самый простой способ простейшей параллелизации разделяемой памяти в цикле python, где массивы numpy управляются с помощью функций numpy/scipy?

Я не ищу наиболее эффективный способ, я просто хотел, чтобы что-то простое реализовано, что не требует значительного перезаписи, когда цикл не запускается параллельно. Точно так же, как OpenMP реализуется на языках более низкого уровня.

Лучший ответ, который я видел в этом отношении, - этот, но это довольно неуклюжий способ, который требует, чтобы он выражал цикл в функцию, которая принимает один аргумент, несколько строк преобразования с общим массивом crud, похоже, требует, чтобы параллельная функция вызывалась из __main__, и, похоже, она не работает из интерактивного приглашения (где я трачу много времени).

При всей простоте Python это действительно лучший способ parellelise loop? В самом деле? Это что-то тривиальное для параллелизма в модуле OpenMP.

Я внимательно прочитал непрозрачную документацию модуля многопроцессорности, только чтобы узнать, что он настолько общий, что он подходит ко всему, кроме простой параллелизации цикла. Я не заинтересован в настройке менеджеров, прокси, труб и т.д. У меня просто простой цикл, полностью параллельный, который не имеет никакой связи между задачами. Использование MPI для параллелизации такой простой ситуации кажется излишним, не говоря уже о том, что в этом случае это будет неэффективно.

У меня не было времени узнать о множестве разных параллельных пакетов с разделяемой памятью для Python, но было интересно, есть ли у кого-то больше опыта в этом и может показать мне более простой способ. Пожалуйста, не предлагайте методы последовательной оптимизации, такие как Cython (я уже использую его), или используя параллельные функции numpy/scipy, такие как BLAS (мой случай более общий и более параллельный).

4b9b3361

Ответ 1

С параллельной поддержкой Cython:

# asd.pyx
from cython.parallel cimport prange

import numpy as np

def foo():
    cdef int i, j, n

    x = np.zeros((200, 2000), float)

    n = x.shape[0]
    for i in prange(n, nogil=True):
        with gil:
            for j in range(100):
                x[i,:] = np.cos(x[i,:])

    return x

На двухъядерной машине:

$ cython asd.pyx
$ gcc -fPIC -fopenmp -shared -o asd.so asd.c -I/usr/include/python2.7
$ export OMP_NUM_THREADS=1
$ time python -c 'import asd; asd.foo()'
real    0m1.548s
user    0m1.442s
sys 0m0.061s

$ export OMP_NUM_THREADS=2
$ time python -c 'import asd; asd.foo()'
real    0m0.602s
user    0m0.826s
sys 0m0.075s

Это работает отлично параллельно, так как np.cos (как и другие ufunc) освобождает GIL.

Если вы хотите использовать это в интерактивном режиме:

# asd.pyxbdl
def make_ext(modname, pyxfilename):
    from distutils.extension import Extension
    return Extension(name=modname,
                     sources=[pyxfilename],
                     extra_link_args=['-fopenmp'],
                     extra_compile_args=['-fopenmp'])

и (сначала удалите asd.so и asd.c):

>>> import pyximport
>>> pyximport.install(reload_support=True)
>>> import asd
>>> q1 = asd.foo()
# Go to an editor and change asd.pyx
>>> reload(asd)
>>> q2 = asd.foo()

Итак, да, в некоторых случаях вы можете распараллеливать только с помощью потоков. OpenMP - это просто причудливая оболочка для потоковой передачи, поэтому Cython здесь нужен только для более простого синтаксиса. Без Cython вы можете использовать модуль threading --- работает так же, как многопроцессорность (и, вероятно, более надежно), но вам не нужно делать ничего особенного, чтобы объявлять массивы как разделяемую память.

Однако не все операции освобождают GIL, поэтому YMMV для производительности.

***

И еще одна полезная ссылка, очищенная от других ответов Stackoverflow --- другой интерфейс для многопроцессорности: http://packages.python.org/joblib/parallel.html

Ответ 2

Использование операции отображения (в данном случае multiprocessing.Pool.map()) является более или менее каноническим способом паралеллизации цикла на одной машине. Если и до тех пор, пока встроенный map() не будет параллелизирован.

Обзор различных возможностей можно найти здесь.

Вы можете использовать openmp с python (или, скорее, cython), но это выглядит не так просто.

IIRC, точка, если только запуск многопроцессорных материалов из __main__ является обязательным условием из-за совместимости с Windows. Поскольку в Windows не хватает fork(), он запускает новый интерпретатор python и должен импортировать в него код.

Edit

Numpy может парализовать некоторые операции, такие как dot(), vdot() и innerproduct(), при настройке с хорошей многопоточной библиотекой BLAS, например, OpenBLAS. (См. Также этот вопрос.)

Так как операции массива numpy в основном по элементу, представляется возможным их распараллеливать. Но это будет включать настройку сегмента разделяемой памяти для объектов python или деление массивов на части и подачу их на разные процессы, в отличие от того, что делает multiprocessing.Pool. Независимо от того, какой подход будет предпринят, это повлечет за собой нехватку памяти и обработки данных для управления всем этим. Нужно было бы провести обширные тесты, чтобы увидеть, для каких размеров массивов это действительно будет стоить усилий. Результат этих тестов, вероятно, будет значительным для каждой аппаратной архитектуры, операционной системы и объема оперативной памяти.

Ответ 3

. map() метод класса mathDict() в ParallelRegression делает именно то, что вы ищете в двух строках кода, которые должны быть очень легкими в интерактивной подсказке. Он использует истинную многопроцессорность, поэтому требование о том, чтобы функция, которая должна выполняться параллельно, была способна к разборке, неизбежна, но это обеспечивает простой способ петли над матрицей в общей памяти из нескольких процессов.

Скажите, что у вас есть функция разборки:

def sum_row( matrix, row ):
    return( sum( matrix[row,:] ) )

Затем вам просто нужно создать объект mathDict(), представляющий его, и использовать mathDict().map():

matrix = np.array( [i for i in range( 24 )] ).reshape( (6, 4) )

RA, MD = mathDictMaker.fromMatrix( matrix, integer=True )
res = MD.map( [(i,) for i in range( 6 )], sum_row, ordered=True )

print( res )
# [6, 22, 38, 54, 70, 86]

В документации (ссылка выше) объясняется, как передать комбинацию аргументов positional и keyword в вашу функцию, включая саму матрицу в любой позиции или в качестве аргумента ключевого слова. Это должно позволить вам использовать практически любую функцию, которую вы уже написали, не изменяя ее.