Q:scikit-learn 是如何处理它们的?
所以,让我们从 2019/4Q 的文档开始:
n_jobs:int 或 None,可选(默认 = None)
并行运行的作业数。 None 表示 1,除非在 joblib.parallel_backend context 中。 -1 表示使用所有处理器。详情请见Glossary。
我们在这里有哪些替代方案?哪个是最好的?
A)完全没有并行性
B)锁定 CPU 而不是提高性能
C)USTOM 设置 CPU - 核心映射以获得最佳性能
D)Dask-nodes 集群中的 ITRIBUTE 工作负载
选项 A : A)完全没有并行性
因此,可以明确避免并行处理RandomisedSearchCV(Pipeline([(…),(…,SGDClassifier( n_jobs=13,…)),]),…,n_jobs=13,… ) 用于 CPU 密集型但受 MEM 限制的任务处理,通过显式使用 "threading"-后端上下文构造函数:
with parallel_backend( 'threading' ): # also ref.'d via sklearn.utils.parallel_backend
grid2.fit( … )
在这里,无论有多少线程被实例化,都只是在等待一个,central GIL-lock。 所有等待,但一个执行。这是一个已知的 GIL-lock-introduced re-[SERIAL]-isation 任何基于线程的代码执行到 更昂贵的,只是交错的,纯粹的-[SERIAL]python 代码执行。除了那些(不是这个),主要是延迟屏蔽技巧(为了更好地利用 I/O 绑定任务在 NOP 中花费的时间,等待完成并产生任何结果的 I/O 操作),这无助于更快地充分利用基于 python 的 ML 管道,但是非常对面。
选项 B : B)锁定 CPU 而不是提高性能
人们可以选择一个更好的、不那么令人望而却步的后端 - "multiprocessing" 或最近的 joblib-releases 也可以是 "loky",其中没有 GIL 锁给我们带来了麻烦(以实例化 n_jobs-many python-process 副本为代价,每个副本都有其内部和不可避免的 GIL 锁,现在至少不会与其他自己的线程竞争一个通过首先获取 GIL 锁来执行时间片的工作量),但这里的故事还没有结束。这个选项是一个典型的例子,当多个级别的 n_jobs 感知处理出现在同一个管道中时 - 他们每个人都在争夺(在 O/S 调度程序级别)以获得CPU 内核时间片在分时运行中的进程多于 CPU 内核的数量。 结果? 进程已生成,但必须在队列中等待轮到它们(如果用户允许的内核数量超过 - 不仅要检查内核数量,还要检查允许的内核数量) CPU-core-affinity 设置,由操作系统为给定的用户/进程有效权限强制执行,在严格管理的系统上可能远小于 CPU-cores 的物理(或虚拟化模拟)数量),浪费时间,丢失 CPU 缓存预取的数据块(因此一次又一次地花费昂贵的 RAM 获取(每次支付 ~300~350 ns),而不是重新使用预取(并且已经付费)来自 L1/L2/L3 缓存的数据,成本仅为 0.5 ns(!))
选项 C:C)USTOM 设置 CPU 内核映射以获得最佳性能
一个好的工程实践是仔细映射 CPU 内核以进行处理。
如果有一个合适的后端,就必须确定性能瓶颈在哪里 - 在这里,最有可能(如果有选项 D,则有可能例外)配备了一个巨大的强和胖内存机器的集群),一个人会更喜欢让每一个 SGDClassifier.fit() 更快(花费更多n_jobs-specified process-instances for most昂贵的子任务 - 训练),而不是拥有“更多” RandomisedSearchCV()-initated “玩具”在操场上,但因内存不足和 CPU 缓存效率低下而窒息。
即使在不知道所有细节的幕后,您的代码也总是必须“服从”才能在所有 CPU 内核上运行,而只能在那些允许被任何此类multiprocessing 使用的 CPU 内核上运行-请求的子流程,
数量不高于:len( os.sched_getaffinity( 0 ) )。如果对详细信息感兴趣,请阅读并使用代码here。
一般来说,良好的规划和分析实践将有助于实现n_jobs-instantiated 进程映射到可用 CPU 核心集的最佳合理配置。没有什么魔法,只有常识、过程监控和运行时的基准测试/计时将帮助我们提高这种能力。
选项 D:D)在Dask-nodes 的集群中分配工作负载
在可能的情况下,using Dask-module 启用 distributed-computing 节点,可以设置:
with parallel_backend( 'dask' ):
grid2.fit( … )
这将利用所有 Dask 集群计算资源来使“繁重”任务更智能地完成,而不是仅使用 localhost-CPU/RAM 资源。最终,这是 Python 生态系统在其当前原样状态下可能实现的最大并发处理级别。