Python 为什么joblib并行执行会使运行时慢得多?
我想在3DPython 为什么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
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))
####################################################################