python - 为什么 joblib 并行执行会使运行时慢很多?

标签 python performance parallel-processing joblib low-latency

我想打乱 3D numpy 数组中的值,但前提是它们 > 0。

当我用单核运行我的函数时,它甚至比使用 2 个核快得多。这远远超出了创建新 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()

MASK = "time executing in parallel with num_cores={}:\t {: >4.4f}s"

tic=time.perf_counter() #----------------------------- [PERF-me]

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() #----------------------------- [PERF-me]

print( MASK.format(num_cores,toc-tic) )

最佳答案

Q : "What am I missing?"

很可能是内存 I/O 瓶颈。

enter image description here

虽然处理的 numpy 部分在这里似乎很浅(shuffle 不计算一点,但在一对位置之间移动数据,不是吗?),对于大多数时候,这将不允许“足够的时间”(通过做任何有用的工作)来让内存 I/O 被重新排序的 CPU 核心指令屏蔽(引用 latency-costs 直接 +当代超标量 CISC 架构最低级别的跨 QPI 内存 I/O 操作具有高度推测性分支预测(对内存 I/O 绑定(bind)非分支紧密制作的部分没有用)和多核和多核核心 NUMA 设计)。

这很可能就是为什么即使是第一次分拆 concurrent process (无论是否强制驻扎在相同的 (这里是通过两步舞蹈过程的交错对共享 CPU 核心时间,同样是内存 I/O 限制,更糟糕的机会共享内存 I/O channel 上的延迟屏蔽...) 或任何其他 (如果必须执行非本地内存,此处会添加跨 QPI 附加延迟成本-I/O,内存 I/O 延迟屏蔽的机会再次恶化) CPU 核心。

CPU 内核跳跃,由 CPU 时钟加速策略的冲突效应强制执行(稍后开始违反热管理,因此将进程跳到下一个更冷的 CPU 内核上)将使所有 CPU 内核无效缓存的好处,因为没有预缓存的数据在下一个更冷的核心可用,因此必须再次重新获取所有(一旦预缓存已经进入最快的 L1 数据缓存)数据(也许,对于具有更大的数组对象内存占用,甚至需要跨 QPI 获取),因此利用更多内核不会对最终效率产生微不足道的影响。

enter image description here

;o)
numpy 高性能和智能处理在这里不是应该受到指责的 - 恰恰相反 - 它清楚地消除了 CPU“饥饿”状态 - 多年来被认为是我们所有现代 CPU 的性能天花板 - 这就是为什么我们看到如此多的核心 CPU,它们试图通过拥有越来越多的核心来规避这一瓶颈 -请参阅上面引用的评论硅级分析。

最后但并非最不重要
代码原样包含大量改进其性能的机会,numpy- smart-vectorised 是第一个提到的,避免了 range() 循环,所以还有更多的技巧可以遵循,所有这些最终都会让 headbang 陷入同样的​​麻烦——CPU 饥饿天花板

关于python - 为什么 joblib 并行执行会使运行时慢很多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65026499/

相关文章:

"and"、 "&&"和 "bitand"之间的 C++ 区别

.net - 如何重构/重建以 WinForms 风格开发的 WPF 应用程序

c++ - 如何根据先前的行为预测系统的行为

c++ - 嵌套锁和简单锁的区别

python - 在 jsonpath-ng python 中使用 OR 运算符 (|)

python - 从日期字符串列中提取年份部分

javascript - 在python中使用selenium执行javascript console.log的确切方法

python - 如何在给定语法规则的情况下生成所有可能的字符串?

r - 在 R 中执行多个逻辑比较的最快方法是什么?

java - 减少 Java 中同步块(synchronized block)的范围意外地损坏了我的 ArrayList,为什么会出现这种情况?