почему параллельное выполнение joblib значительно замедляет время выполнения?

Я хочу перетасовать значения в трехмерном numpy-массиве, но только когда они ›0.

Когда я запускаю свою функцию с одним ядром, это намного быстрее, чем даже с двумя ядрами. Это выходит далеко за рамки накладных расходов на создание новых процессов Python. Что мне не хватает?

Следующий код выводит:

random shuffling of markers started
time in serial execution:                          1.0288s
time executing in parallel with num_cores=1:       0.9056s
time executing in parallel with num_cores=2:     273.5253s
import numpy as np
import time
from random import shuffle
from joblib import Parallel, delayed  
import multiprocessing

import numpy as np

def randomizeVoxels(V,markerLUT):
    V_rand=V.copy()
    # the xyz naming here does not match outer convention, which will depend on permutation
    for ix in range(V.shape[0]):
        for iy in range(V.shape[1]):
            if V[ix,iy]>0:
                V_rand[ix,iy]=markerLUT[V[ix,iy]]

    return V_rand

V_ori=np.arange(1000000,-1000000,-1).reshape(100,100,200)

V_rand=V_ori.copy()

listMarkers=np.unique(V_ori)
listMarkers=[val for val in listMarkers if val>0]

print("random shuffling of markers started\n")

reassignedMarkers=listMarkers.copy()
#random shuffling of original markers
shuffle(reassignedMarkers)

markerLUT={}
for i,iMark in enumerate(listMarkers):
    markerLUT[iMark]=reassignedMarkers[i]

tic=time.perf_counter()

for ix in range(len(V_ori)):
    for iy in range(len(V_ori[0])):
        for iz in range(len(V_ori[0][0])):
            if V_ori[ix,iy,iz]>0:
                V_rand[ix,iy,iz]=markerLUT[V_ori[ix,iy,iz]]

toc=time.perf_counter()

print("time in serial execution: \t\t\t{: >4.4f} s".format(toc-tic))

#######################################################################3

num_cores = 1

V_rand=V_ori.copy()

tic=time.perf_counter()

results= Parallel(n_jobs=num_cores)\
    (delayed(randomizeVoxels)\
        (V_ori[imSlice,:,:],
        markerLUT
        )for imSlice in range(V_ori.shape[0]))

for i,resTuple in enumerate(results):
    V_rand[i,:,:]=resTuple

toc=time.perf_counter() 

print("time executing in parallel with num_cores={}:\t{: >4.4f} s".format(num_cores,toc-tic))    

####################################################################

num_cores = 2

V_rand=V_ori.copy()

tic=time.perf_counter()

results= Parallel(n_jobs=num_cores)\
    (delayed(randomizeVoxels)\
        (V_ori[imSlice,:,:],
        markerLUT
        )for imSlice in range(V_ori.shape[0]))

for i,resTuple in enumerate(results):
    V_rand[i,:,:]=resTuple

toc=time.perf_counter() 

print("time executing in parallel with num_cores={}:\t {: >4.4f}s".format(num_cores,toc-tic))    

####################################################################



person FacundoLM2    schedule 26.11.2020    source источник


Ответы (1)


В: Что мне не хватает?

Скорее всего, узкие места ввода-вывода памяти.

введите описание изображения здесь

Хотя numpy-часть обработки здесь кажется довольно мелкой (перемешивание не вычисляет бит, но перемещает данные между парой мест, не так ли?), В большинстве случаев это не позволяет времени -достаточно (выполняя любую полезную работу), чтобы маскировать операции ввода-вывода памяти с помощью переупорядоченных инструкций ядра процессора (см. latency-cost для прямых операций ввода-вывода памяти с кросс-QPI на самых низких уровнях современных супер- скалярные архитектуры CISC с высоко спекулятивными предсказаниями ветвлений (бесполезны для жестко созданных разделов без ветвления с привязкой к памяти ввода-вывода) и многоядерных и многоядерных схем NUMA).

Скорее всего, поэтому даже первый побочный продукт параллельный процесс (независимо от того, принудительно ли он используется для размещения одного и того же (здесь время разделяемого ядра ЦП парой чередующихся двухэтапных танцевальных процессов, снова память -Привязка ввода-вывода, с еще худшими шансами на маскировку задержки на каналах ввода-вывода с общей памятью ...) или любой другой (здесь добавление кросс-QPI add- на затраты на задержку при необходимости выполнять ввод-вывод нелокальной памяти, что опять же ухудшает шансы на маскировку задержки ввода-вывода памяти) CPU-core.

Скачкообразное переключение между ядрами ЦП, вызванное конфликтующими эффектами политики повышения тактовой частоты ЦП (позже начинает нарушать управление температурным режимом, таким образом, перескакивая процесс на следующее, более холодное ядро ​​ЦП), аннулируются все преимущества кеш-памяти ядра ЦП. из-за отсутствия предварительно кэшированных данных на следующем, более холодном, доступном ядре, что приводит к необходимости повторной выборки всех (уже предварительно кэшированных в самый быстрый кеш L1data) снова (возможно, для объектов-массивов с большими объемами памяти, имея даже необходимость перекрестной выборки QPI), поэтому использование большего количества ядер не оказывает тривиального влияния на итоговую эффективность.

введите описание изображения здесь

; o)
Здесь нельзя винить numpy высокую производительность и интеллектуальную обработку - как раз наоборот - она ​​явно демаскирует состояние голодания ЦП - известное тем, что Возраст, чтобы быть Самым верхним пределом производительности для всех наших современных процессоров - вот почему мы видим столько многоядерных процессоров, которые пытаются обойти это узкое место, имея все больше и больше ядер - см. прокомментированный анализ уровня кремния, упомянутый выше.

И последнее, но не менее важное: код как есть содержит огромное количество возможностей для повышения его производительности, первым назвав numpy-smart-vectorised, избегая range()-циклов, поэтому есть еще несколько советов, которым нужно следовать, все это в конечном итоге приведет к одной и той же проблеме - потолку нехватки ЦП

person user3666197    schedule 26.11.2020