Python - параллелизация параллельных операций с помощью joblib

Мне бы хотелось, чтобы какая-то помощь поняла, что я сделал/почему мой код не работает, как я ожидал.

Я начал использовать joblib, чтобы попытаться ускорить мой код, запустив параллельный цикл (большой).

Я использую его так:

from joblib import Parallel, delayed
def frame(indeces, image_pad, m):

    XY_Patches = np.float32(image_pad[indeces[0]:indeces[0]+m, indeces[1]:indeces[1]+m,  indeces[2]])
    XZ_Patches = np.float32(image_pad[indeces[0]:indeces[0]+m, indeces[1],                  indeces[2]:indeces[2]+m])
    YZ_Patches = np.float32(image_pad[indeces[0],                 indeces[1]:indeces[1]+m,  indeces[2]:indeces[2]+m])

    return XY_Patches, XZ_Patches, YZ_Patches


def Patch_triplanar_para(image_path, patch_size):

    Image, Label, indeces =  Sampling(image_path)

    n = (patch_size -1)/2
    m = patch_size

    image_pad = np.pad(Image, pad_width=n, mode='constant', constant_values = 0)

    A = Parallel(n_jobs= 1)(delayed(frame)(i, image_pad, m) for i in indeces)
    A = np.array(A)
    Label = np.float32(Label.reshape(len(Label), 1))
    R, T, Y =  np.hsplit(A, 3)

    return R, T, Y, Label

Я экспериментировал с "n_jobs", ожидая, что увеличение этого ускорит мою функцию. Однако, когда я увеличиваю n_jobs, ситуация значительно замедляется. При запуске этого кода без "Параллельного" все происходит медленнее, пока я не увеличиваю количество заданий с 1.

Почему это так? Я понял, что чем больше заданий я запускаю, тем быстрее script? я использую это неправильно?

Спасибо!

Ответ 1

Возможно, ваша проблема вызвана тем, что image_pad - это большой массив. В вашем коде используется бэкэнд multiprocessing по умолчанию joblib. Этот бэкэнд создает пул работников, каждый из которых является процессом Python. Входные данные в функцию затем копируются n_jobs раз и передаются каждому работнику в пуле, что может привести к серьезным накладным расходам. Цитирование из joblib docs:

По умолчанию рабочие пула являются реальными процессами Python, раздвоенными с использованием модуля многопроцессорности стандартной библиотеки Python, когда n_jobs!= 1. Аргументы, переданные в качестве входа в вызов Parallel, сериализуются и перераспределяются в памяти каждого рабочего процесса.

Это может быть проблематично для больших аргументов, так как они будут переназначать n_jobs раз рабочими.

Поскольку эта проблема часто встречается в научных вычислениях с основами данных на основе numpy, joblib.Parallel предоставляет специальную обработку для больших массивов, чтобы автоматически сбрасывать их в файловой системе и передавать ссылку на работника, чтобы открыть их как карту памяти в этом файле используя подкласс numpy.memmap numpy.ndarray. Это позволяет совместно использовать сегмент данных между всеми рабочими процессами.

Примечание. Следующее применимо только к бэкэнду по умолчанию для многопроцессорности. Если ваш код может освободить GIL, то использование backend = "threading" еще более эффективно.

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

docs говорят, что joblib предоставляет автоматическое преобразование memmap, которое может быть полезно.

Ответ 2

Вполне возможно, что проблема, с которой вы сталкиваетесь, является фундаментальной для характера компилятора python.

Если вы читаете " https://www.ibm.com/developerworks/community/blogs/jfp/entry/Python_Is_Not_C?lang=en, вы можете увидеть от профессионала, который специализируется на оптимизации и параллелизации кода питона, который выполняет итерацию через большие циклы - это по сути медленная операция для выполнения потока python. Таким образом, нерест больше процессов, проходящих через массивы, только замедляет работу.

Однако - есть вещи, которые можно сделать.

Cython и Numba компиляторы предназначены для оптимизации кода, который похож на стиль C/С++ (т.е. ваш случай) - в частности, Numba new @vectorise декораторы позволяют скалярные функции для одновременного ввода и применения операций на больших массивах с большими массивами (target=Parallel).

Я недостаточно понимаю ваш код, чтобы привести пример реализации, но попробуйте это! Эти компиляторы, используемые правильными способами, привели к увеличению скорости на 3000 000% для меня для параллельных процессов в прошлом!