Python 为什么joblib并行执行会使运行时慢得多?

Python 为什么joblib并行执行会使运行时慢得多?,python,parallel-processing,joblib,Python,Parallel Processing,Joblib,我想在3Dnumpy-数组中洗牌值,但仅当值>0时 当我用一个内核运行我的函数时,它比用两个内核运行要快得多。这远远超出了创建新python进程的开销。我错过了什么 以下代码输出: random shuffling of markers started time in serial execution: 1.0288s time executing in parallel with num_cores=1: 0.9056s time

我想在3D
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
Q:“我错过了什么?”

很可能是内存I/O瓶颈

虽然这里处理的
numpy
-部分似乎很肤浅(shuffle不会计算一点,而是在一对位置之间移动数据,不是吗?),但在大多数情况下,这将不允许“足够的时间”(通过做任何有用的工作),从而使内存I/O被重新排序的CPU核心指令屏蔽(参考:当代超标量CISC体系结构最低级别的直+交叉QPI内存I/O操作,具有高度推测性的分支预测(不适用于内存I/O绑定的非分支严密构造部分)和多核及多核NUMA设计)

这很可能就是为什么即使是第一个剥离(无论是否强制在同一个剥离上露营(这里是一对交错的两步舞动进程共享CPU核心时间,再次限制内存I/O,在共享内存I/O通道上延迟屏蔽的可能性更大…)或其他原因(在这里,如果必须执行非本地内存I/O,则增加交叉QPI附加组件延迟成本,从而再次恶化内存I/O延迟掩蔽的可能性)CPU核心

CPU时钟提升策略的碰撞效应(随后开始违反热管理,从而将进程跳跃到下一个更冷的CPU内核上)强制执行的CPU内核跳跃将使所有CPU内核缓存优势无效,因为下一个更冷的内核上的预缓存数据不可用,因此必须重新获取所有CPU内核(一旦预缓存到最快的L1data缓存中)数据再次存储(可能对于内存占用更大的阵列对象,甚至需要跨QPI获取),因此利用更多的内核不会对最终的效率产生微不足道的影响

(o)
在这里,高性能和智能处理并不是罪魁祸首,恰恰相反,它明显降低了CPU的“饥饿”状态—众所周知,它是我们所有现代CPU的性能上限—这就是为什么我们看到如此多的核心CPU,它们试图通过拥有越来越多的核心来绕过这一瓶颈—请参见上面提到的评论硅级分析。

最后但并非最不重要的
原样的代码包含了大量改进it的性能,
numpy
-智能矢量化是第一个命名的,避免了
range()
-循环,因此有更多的提示需要遵循,所有这些最终都会让headbang陷入同样的麻烦-CPU不足上限

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))    

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