多处理:只使用物理内核?
Multiprocessing: use only the physical cores?
我有一个函数 foo
,它消耗大量内存,我想 运行 并行执行多个实例。
假设我有一个 CPU 有 4 个物理内核,每个物理内核有两个逻辑内核。
我的系统有足够的内存来并行容纳 4 个 foo
实例,但不能容纳 8 个实例。此外,由于这 8 个内核中有 4 个是逻辑内核,我也不希望使用所有 8 个内核会提供比仅使用 4 个物理的收获更多。
所以我想 运行 foo
在 4 个物理核心上 仅 。换句话说,我想确保做 multiprocessing.Pool(4)
(4 是由于内存限制我可以在这台机器上容纳的函数的最大并发数 运行 )将作业分派给四个物理核心(而不是,例如,两个物理核心及其两个逻辑后代的组合)。
如何在 python 中做到这一点?
编辑:
我之前使用了 multiprocessing
中的代码示例,但我对库一无所知,因此为了避免混淆,我删除了它。
注意:此方法不适用于windows,仅在linux 上测试过。
使用multiprocessing.Process
:
使用 Process()
时,为每个进程分配一个物理内核非常容易。您可以创建一个 for 循环,遍历每个核心并使用 taskset -p [mask] [pid]
将新进程分配给新核心:
import multiprocessing
import os
def foo():
return
if __name__ == "__main__" :
for process_idx in range(multiprocessing.cpu_count()):
p = multiprocessing.Process(target=foo)
os.system("taskset -p -c %d %d" % (process_idx % multiprocessing.cpu_count(), os.getpid()))
p.start()
我的工作站上有 32 个内核,因此我将部分结果放在这里:
pid 520811's current affinity list: 0-31
pid 520811's new affinity list: 0
pid 520811's current affinity list: 0
pid 520811's new affinity list: 1
pid 520811's current affinity list: 1
pid 520811's new affinity list: 2
pid 520811's current affinity list: 2
pid 520811's new affinity list: 3
pid 520811's current affinity list: 3
pid 520811's new affinity list: 4
pid 520811's current affinity list: 4
pid 520811's new affinity list: 5
...
如你所见,这里是每个进程的新旧亲缘关系。第一个用于所有核心 (0-31),然后分配给核心 0,第二个进程默认分配给核心 0,然后其亲和力更改为下一个核心 (1),依此类推。
使用multiprocessing.Pool
:
警告:此方法需要调整 pool.py
模块,因为据我所知您无法从 Pool()
中提取 pid .此外,此更改已在 python 2.7
和 multiprocessing.__version__ = '0.70a1'
.
上进行了测试
在Pool.py
中找到调用_task_handler_start()
方法的那一行。在下一行中,您可以使用(我将 import os
放在这里以便 reader 不会忘记导入它)将池中的进程分配给每个 "physical" 核心:
import os
for worker in range(len(self._pool)):
p = self._pool[worker]
os.system("taskset -p -c %d %d" % (worker % cpu_count(), p.pid))
大功告成。测试:
import multiprocessing
def foo(i):
return
if __name__ == "__main__" :
pool = multiprocessing.Pool(multiprocessing.cpu_count())
pool.map(foo,'iterable here')
结果:
pid 524730's current affinity list: 0-31
pid 524730's new affinity list: 0
pid 524731's current affinity list: 0-31
pid 524731's new affinity list: 1
pid 524732's current affinity list: 0-31
pid 524732's new affinity list: 2
pid 524733's current affinity list: 0-31
pid 524733's new affinity list: 3
pid 524734's current affinity list: 0-31
pid 524734's new affinity list: 4
pid 524735's current affinity list: 0-31
pid 524735's new affinity list: 5
...
请注意,此修改 pool.py
将作业循环分配给核心。因此,如果您分配的作业多于 cpu 核,您最终将在同一个核上分配多个作业。
编辑:
OP 正在寻找的是 pool()
能够在特定内核上启动池。为此,需要对 multiprocessing
进行更多调整(首先撤消上述更改)。
警告:
不要尝试复制粘贴函数定义和函数调用。仅复制粘贴应该在 self._worker_handler.start()
之后添加的部分(您将在下面看到)。注意我的multiprocessing.__version__
告诉我版本是'0.70a1'
,不过没关系,只要你添加你需要添加的即可:
multiprocessing
的pool.py
:
向 __init__()
定义添加一个 cores_idx = None
参数。在我的版本中,添加后它看起来像这样:
def __init__(self, processes=None, initializer=None, initargs=(),
maxtasksperchild=None,cores_idx=None)
你还应该在self._worker_handler.start()
之后添加以下代码:
if not cores_idx is None:
import os
for worker in range(len(self._pool)):
p = self._pool[worker]
os.system("taskset -p -c %d %d" % (cores_idx[worker % (len(cores_idx))], p.pid))
multiprocessing
的__init__.py
:
将 cores_idx=None
参数添加到 Pool()
的定义以及 return 部分中的其他 Pool()
函数调用。在我的版本中它看起来像:
def Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None,cores_idx=None):
'''
Returns a process pool object
'''
from multiprocessing.pool import Pool
return Pool(processes, initializer, initargs, maxtasksperchild,cores_idx)
大功告成。以下示例仅在核心 0 和 2 上运行一个包含 5 个工作线程的池:
import multiprocessing
def foo(i):
return
if __name__ == "__main__":
pool = multiprocessing.Pool(processes=5,cores_idx=[0,2])
pool.map(foo,'iterable here')
结果:
pid 705235's current affinity list: 0-31
pid 705235's new affinity list: 0
pid 705236's current affinity list: 0-31
pid 705236's new affinity list: 2
pid 705237's current affinity list: 0-31
pid 705237's new affinity list: 0
pid 705238's current affinity list: 0-31
pid 705238's new affinity list: 2
pid 705239's current affinity list: 0-31
pid 705239's new affinity list: 0
当然,通过删除 cores_idx
参数,您仍然可以拥有 multiprocessing.Poll()
的常用功能。
我找到了一个不涉及更改 python 模块源代码的解决方案。它使用建议的方法 here。一个人只能检查
通过执行以下操作 运行 执行该脚本后物理核心处于活动状态:
lscpu
在bashreturns:
CPU(s): 8
On-line CPU(s) list: 0,2,4,6
Off-line CPU(s) list: 1,3,5,7
Thread(s) per core: 1
[可以从 python] 中 运行 上面链接的脚本。在任何情况下,在 运行 上面的脚本之后,在 python:
中输入这些命令
import multiprocessing
multiprocessing.cpu_count()
returns 4.
我知道这个话题现在已经很老了,但是当在 google 中输入 'multiprocessing logical core' 时它仍然显示为第一个答案...我觉得我必须给出一个额外的答案,因为我看2018年(甚至更晚..)的人可能会在这里很容易混淆(有些答案确实有点混乱)
我认为没有比这里更好的地方来警告读者上面的一些答案了,很抱歉让这个话题复活。
--> 计算 CPU 秒 (LOGICAL/PHYSICAL) 使用 PSUTIL 模块
对于 ex 的 4 物理核心/8 线程 i7,它将 return
import psutil
psutil.cpu_count(logical = False)
4
psutil.cpu_count(logical = True)
8
就这么简单。
在那里,您不必担心 OS、平台、硬件本身或其他任何问题。 我相信它比 multiprocessing.cpu_count() 好得多,后者有时会给出奇怪的结果,至少从我自己的经验来看是这样。
--> 要使用 N 物理内核(由您选择)使用 YUGI
描述的多处理模块
只要数一数你有多少个物理进程,启动一个 multiprocessing.Pool 4 个工人。
或者你也可以尝试使用joblib.Parallel()函数
2018 年的 joblib 不是 python 标准发行版的一部分,而只是 Yugi 描述的多处理模块的包装器。
--> MOST OF THE TIME,不要使用超过可用的核心(除非你已经对非常具体的代码进行了基准测试并证明它是值得的)
我们可以到处听到(也有一些人在这里回答)"the OS will take care properly is you use more core than available"。 绝对是 100% 假。如果您使用的核心多于可用的核心,您将面临巨大的性能下降。因为 OS 调度器会尽量以同样的注意力去处理每一个任务,有规律地从一个任务切换到另一个任务,并且取决于 OS,它最多可以花费 100% 的工作时间只是在进程之间切换,这将是灾难性的。
不要只相信我:尝试一下,对其进行基准测试,您会发现它有多清晰。
POS是否可以决定代码将在逻辑内核还是物理内核上执行?
如果你问这个问题,这意味着你不了解物理和逻辑核心的设计方式,所以也许你应该多检查一下处理器的架构。
例如,如果您想 运行 在核心 3 而不是核心 1 上,那么我想确实有一些解决方案,但只有当您知道如何编写 OS 的代码时才可用内核和调度程序,如果您问这个问题,我认为情况并非如此。
如果您在 4 个物理处理器/8 个逻辑处理器上启动 4 个 CPU-intensive 进程,调度程序会将您的每个进程分配给 1 个不同的物理核心(并且 4 个逻辑核心将保持 not/poorly 使用).但是在 4 逻辑/8 线程 proc 上,如果处理单元是 (0,1) (1,2) (2,3) (4,5) (5,6) (6,7),那么它不会进程在 0 或 1 上执行的区别:它是相同的处理单元。
至少据我所知(但专家可以确认/证实,也许它也不同于非常具体的硬件规格)我认为在 0 或 1 上执行代码之间没有或只有很小的区别。在处理中unit (0,1),我不确定 0 是逻辑值而 1 是物理值,还是 vice-versa。根据我的理解(这可能是错误的),两者都是来自同一个处理单元的处理器,它们只是共享它们的高速缓存/对硬件(包括 RAM)的访问,并且 0 不比 1 多一个物理单元。
除此之外,您应该让 OS 决定。因为 OS 调度程序可以利用某些平台(例如 i7、i5、i3...)上存在的硬件 logical-core 涡轮增压,所以您没有打开其他电源,这可能对你真正有帮助。
如果您在 4 个物理内核/8 个逻辑内核上启动 5 个 CPU-intensive 任务,行为将是混乱的,几乎不可预测,主要取决于您的硬件和 OS。调度程序将尽力而为。几乎每一次,你都将不得不面对非常糟糕的表现。
让我们暂时假设我们仍在谈论 4(8) 经典架构:因为调度程序会尽力而为(因此经常切换属性),根据您正在执行的进程,它可能是在 5 个逻辑内核上启动比在 8 个逻辑内核上启动更糟糕(至少他知道一切都会以 100% 的速度使用,所以迷路了他不会尝试避免它,不会切换得太频繁,并且因此不会因切换而浪费太多时间)。
但 99% 可以肯定(但要确定在您的硬件上进行基准测试)如果您使用的物理核心多于可用的物理核心,几乎所有多处理程序都会 运行 变慢。
很多事情都可以介入...程序,硬件,状态OS,它使用的调度程序,你今天早上吃的水果,你姐姐的名字......如果你对某些事情有疑问,只需对其进行基准测试,没有其他简单的方法可以看出你是否正在失去性能或不是。有时候信息学真的很奇怪。
--> MOST 的时间,额外的逻辑核心在 PYTHON 中确实无用(但并非总是如此)
在 python 中有 2 种主要方法可以执行真正的并行任务。
- 多处理(无法利用逻辑内核)
- 多线程(可以利用逻辑核心)
例如运行 4个并行任务
--> 多处理将创建 4 个不同的 python 解释器。对于它们中的每一个,你必须启动一个 python 解释器,定义 reading/writing 的权限,定义环境,分配大量内存等。让我们按原样说:你将启动一个整体从 0 开始的新程序实例。它可能需要大量时间,因此您必须确保这个新程序能够运行足够长的时间,这样它才值得。
如果你的程序有足够的工作(比如说,至少几秒钟的工作),那么因为 OS 在不同的物理核心上分配 CPU-consumming 进程,它可以工作,你可以获得了很多表演,这很棒。而且因为 OS 几乎总是允许进程在它们之间进行通信(尽管速度很慢),它们甚至可以交换(一点点)数据。
--> 多线程不同。在您的 python 解释器中,它只会创建少量内存,供许多 CPU 共享,并同时处理。它的生成速度要快得多(有时在旧计算机上生成一个新进程可能需要很多秒,而生成一个线程的时间却少得离谱)。您不会创建新进程,但 "threads" 会轻得多。
线程可以非常快速地在线程之间共享内存,因为它们确实在同一内存上一起工作(而在使用不同进程时必须 copied/exchanged)。
但是:为什么我们不能在 MOST 情况下使用多线程?看起来很方便?
在python中有一个非常大的限制:在python解释器中一次只能执行一行python,这被称为GIL(全局解释器锁).所以大多数时候,你甚至会通过使用多线程获得 LOSE 的性能,因为不同的线程将不得不等待访问同一个资源。如果您的代码是纯的 python.
,多线程总是无用甚至更糟
--> 为什么我在使用多进程时不应该使用逻辑内核?
逻辑核心没有自己的内存访问。它们只能处理内存访问及其托管物理处理器的缓存。例如,同一处理单元的逻辑和物理核心很可能(并且经常使用)同时在高速缓存的不同位置上使用相同的 C/C++ 函数。确实使治疗速度大大加快。
但是...这些是 C/C++ 函数! Python 是一个很大的 C/C++ 包装器,它需要比等效的 C++ 代码更多的内存和 CPU。很可能在 2018 年,无论您想做什么,2 个大 python 进程需要的内存和缓存 reading/writing 远远超过单个物理+逻辑单元所能承受的,甚至更多等效的 C/C++ truly-multithreaded 代码会消耗什么。这又一次几乎总是会导致性能下降。请记住,处理器缓存中不可用的每个变量都将花费 x1000 倍的时间来读取内存。如果你的缓存对于 1 个 python 进程来说已经完全满了,猜猜如果你强制 2 个进程使用它会发生什么:他们将一次使用它,并永久切换,导致数据被愚蠢地刷新和re-read 每次切换。当数据正在从内存中读取或写入时,您可能认为您的 CPU "is" 正在工作,但事实并非如此。它在等待数据!什么都不做。
--> 那你怎么利用逻辑核心呢?
正如我所说,由于全局解释器锁,在默认 python 中没有真正的多线程(因此没有真正使用逻辑核心)。您可以在程序的某些部分强制删除 GIL,但我认为如果您不确切知道自己在做什么,最好不要碰它。
删除 GIL 无疑是很多研究的主题(请参阅尝试这样做的实验性 PyPy 或 Cython 项目)。
目前还没有真正的解决方案,因为它比看起来要复杂得多。
我承认,还有另一种可行的解决方案:
- 用 C 语言编写你的函数
- 把它包起来python 使用 ctype
- 使用 python 多线程模块调用包装的 C 函数
这将 100% 有效,您将能够使用 python 中的所有逻辑核心,并使用多线程,而且是真实的。 GIL 不会打扰您,因为您不会执行真正的 python 函数,而是执行 C 函数。
例如,像 Numpy 这样的一些库可以在所有可用的线程上工作,因为它们是用 C 编写的。但是如果你到了这一点,我一直认为考虑在 [=直接182=]++是因为考虑的很远,跟原来的pythonic精神相去甚远。
**--> 不要总是使用所有可用的物理内核 **
我经常看到人们像"Ok I have 8 physical core, so I will take 8 core for my job"。它通常有效,但有时会被证明是一个糟糕的主意,尤其是当您的工作需要大量 I/O 时。
尝试使用 N-1 个核心(再一次,特别是对于 I/O 要求很高的任务),您会发现在 per-task/average 上 100% 的时间,单个任务总是 运行 在 N-1 核心上更快。确实,您的计算机会产生很多不同的东西:USB、鼠标、键盘、网络、硬盘驱动器等......即使在工作站上,周期性任务也会随时在您不知道的后台执行。如果您不让 1 个物理核心来管理这些任务,您的计算将经常中断(从内存中清除/替换回内存),这也会导致性能问题。
你可能会想"Well, background tasks will use only 5% of CPU-time so there is 95% left"。但事实并非如此。
处理器一次处理一个任务。每次切换时,都会浪费大量时间将所有内容放回内存中的位置 cache/registries。然后,如果出于某种奇怪的原因,OS 调度程序过于频繁地进行此切换(这是您无法控制的),所有这些计算时间将永远丢失,您无能为力。
如果(有时会发生)由于某种未知原因,此调度程序问题影响的不是 1 个而是 30 个任务的性能,它可能会导致真正有趣的情况,在 29/30 物理内核上工作可能比在30/30
更多 CPU 并不总是最好的
当您使用 multiprocessing.Pool 时,经常会使用进程之间共享的 multiprocessing.Queue 或管理器队列,以允许它们之间进行一些基本通信。有时(我必须说 100 次,但我重复一遍),以 hardware-dependent 的方式,使用更多 CPU 使进程通信/同步时可能会造成瓶颈。在这些特定情况下,运行 在较低的 CPU 数字上可能会很有趣,或者甚至尝试在更快的处理器上驱逐同步任务(这里我说的是科学密集型计算 运行当然在集群上)。由于多处理通常用于集群,因此您必须注意,出于 energy-saving 目的,集群通常会降低频率。正因为如此,single-core 表现可能 真的 糟糕(由 way-much 更高数量的 CPU 平衡),使问题更糟您将代码从本地计算机(内核少,性能高 single-core)扩展到集群(内核多,性能低 single-core),因为根据 single_core_perf/nb_cpu 比率,您的代码瓶颈,有时真的很烦人
每个人都有尽可能多地使用 CPU 的诱惑。但这些案例的基准是强制性的。
典型情况(例如在数据科学中)是并行处理 N 个进程 运行,并且您想将结果汇总到一个文件中。因为您不能等待工作完成,所以您可以通过特定的编写程序来完成。作者将在他的 multiprocessing.Queue(single-core 和 hard-drive 有限进程)中推送的所有内容写入输出文件。 N个进程填充multiprocessing.Queue.
很容易想象,如果你有 31 CPU 向一个非常慢的 CPU 写入信息,那么你的性能将会下降(如果你克服了系统的能力,可能会出现崩溃)处理临时数据)
-->带回家留言
- 使用 psutil 来计算 logical/physical 个处理器,而不是 multiprocessing.cpu_count() 或任何
- 多处理只能在物理核心上工作(或者至少对其进行基准测试以证明它在您的情况下不是真的)
- 多线程将在逻辑核心上运行,但您必须用 C 编写代码并将您的函数包装起来,或者删除全局锁解释器(每次您这样做时,一只小猫都会在世界某个地方残忍地死去)
- 如果您尝试在纯 python 代码上 运行 多线程,您的性能会大幅下降,因此您应该在 99% 的时间使用多处理
- 除非你的processes/threads有很长时间您可以利用的原因,永远不要使用超过可用数量的核心,如果您想尝试,请正确进行基准测试
- 如果您的任务是 I/O 密集型任务,您应该让 1 个物理核心来处理 I/O,如果您有足够的物理核心,那将是值得的。对于多处理实现,它需要使用 N-1 个物理内核。对于经典的2路多线程来说,就是使用N-2个逻辑核心。
- 如果您需要更多性能,请尝试 PyPy(未准备好生产)或 Cython,甚至用 C 编写代码
最后但并非最不重要,也是最重要的一点:如果你真的追求性能,你应该绝对、始终、始终进行基准测试,而不是猜测任何东西。基准测试经常揭示 st运行ge platform/hardware/driver 您可能不知道的非常具体的行为。
我有一个函数 foo
,它消耗大量内存,我想 运行 并行执行多个实例。
假设我有一个 CPU 有 4 个物理内核,每个物理内核有两个逻辑内核。
我的系统有足够的内存来并行容纳 4 个 foo
实例,但不能容纳 8 个实例。此外,由于这 8 个内核中有 4 个是逻辑内核,我也不希望使用所有 8 个内核会提供比仅使用 4 个物理的收获更多。
所以我想 运行 foo
在 4 个物理核心上 仅 。换句话说,我想确保做 multiprocessing.Pool(4)
(4 是由于内存限制我可以在这台机器上容纳的函数的最大并发数 运行 )将作业分派给四个物理核心(而不是,例如,两个物理核心及其两个逻辑后代的组合)。
如何在 python 中做到这一点?
编辑:
我之前使用了 multiprocessing
中的代码示例,但我对库一无所知,因此为了避免混淆,我删除了它。
注意:此方法不适用于windows,仅在linux 上测试过。
使用multiprocessing.Process
:
使用 Process()
时,为每个进程分配一个物理内核非常容易。您可以创建一个 for 循环,遍历每个核心并使用 taskset -p [mask] [pid]
将新进程分配给新核心:
import multiprocessing
import os
def foo():
return
if __name__ == "__main__" :
for process_idx in range(multiprocessing.cpu_count()):
p = multiprocessing.Process(target=foo)
os.system("taskset -p -c %d %d" % (process_idx % multiprocessing.cpu_count(), os.getpid()))
p.start()
我的工作站上有 32 个内核,因此我将部分结果放在这里:
pid 520811's current affinity list: 0-31
pid 520811's new affinity list: 0
pid 520811's current affinity list: 0
pid 520811's new affinity list: 1
pid 520811's current affinity list: 1
pid 520811's new affinity list: 2
pid 520811's current affinity list: 2
pid 520811's new affinity list: 3
pid 520811's current affinity list: 3
pid 520811's new affinity list: 4
pid 520811's current affinity list: 4
pid 520811's new affinity list: 5
...
如你所见,这里是每个进程的新旧亲缘关系。第一个用于所有核心 (0-31),然后分配给核心 0,第二个进程默认分配给核心 0,然后其亲和力更改为下一个核心 (1),依此类推。
使用multiprocessing.Pool
:
警告:此方法需要调整 pool.py
模块,因为据我所知您无法从 Pool()
中提取 pid .此外,此更改已在 python 2.7
和 multiprocessing.__version__ = '0.70a1'
.
在Pool.py
中找到调用_task_handler_start()
方法的那一行。在下一行中,您可以使用(我将 import os
放在这里以便 reader 不会忘记导入它)将池中的进程分配给每个 "physical" 核心:
import os
for worker in range(len(self._pool)):
p = self._pool[worker]
os.system("taskset -p -c %d %d" % (worker % cpu_count(), p.pid))
大功告成。测试:
import multiprocessing
def foo(i):
return
if __name__ == "__main__" :
pool = multiprocessing.Pool(multiprocessing.cpu_count())
pool.map(foo,'iterable here')
结果:
pid 524730's current affinity list: 0-31
pid 524730's new affinity list: 0
pid 524731's current affinity list: 0-31
pid 524731's new affinity list: 1
pid 524732's current affinity list: 0-31
pid 524732's new affinity list: 2
pid 524733's current affinity list: 0-31
pid 524733's new affinity list: 3
pid 524734's current affinity list: 0-31
pid 524734's new affinity list: 4
pid 524735's current affinity list: 0-31
pid 524735's new affinity list: 5
...
请注意,此修改 pool.py
将作业循环分配给核心。因此,如果您分配的作业多于 cpu 核,您最终将在同一个核上分配多个作业。
编辑:
OP 正在寻找的是 pool()
能够在特定内核上启动池。为此,需要对 multiprocessing
进行更多调整(首先撤消上述更改)。
警告:
不要尝试复制粘贴函数定义和函数调用。仅复制粘贴应该在 self._worker_handler.start()
之后添加的部分(您将在下面看到)。注意我的multiprocessing.__version__
告诉我版本是'0.70a1'
,不过没关系,只要你添加你需要添加的即可:
multiprocessing
的pool.py
:
向 __init__()
定义添加一个 cores_idx = None
参数。在我的版本中,添加后它看起来像这样:
def __init__(self, processes=None, initializer=None, initargs=(),
maxtasksperchild=None,cores_idx=None)
你还应该在self._worker_handler.start()
之后添加以下代码:
if not cores_idx is None:
import os
for worker in range(len(self._pool)):
p = self._pool[worker]
os.system("taskset -p -c %d %d" % (cores_idx[worker % (len(cores_idx))], p.pid))
multiprocessing
的__init__.py
:
将 cores_idx=None
参数添加到 Pool()
的定义以及 return 部分中的其他 Pool()
函数调用。在我的版本中它看起来像:
def Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None,cores_idx=None):
'''
Returns a process pool object
'''
from multiprocessing.pool import Pool
return Pool(processes, initializer, initargs, maxtasksperchild,cores_idx)
大功告成。以下示例仅在核心 0 和 2 上运行一个包含 5 个工作线程的池:
import multiprocessing
def foo(i):
return
if __name__ == "__main__":
pool = multiprocessing.Pool(processes=5,cores_idx=[0,2])
pool.map(foo,'iterable here')
结果:
pid 705235's current affinity list: 0-31
pid 705235's new affinity list: 0
pid 705236's current affinity list: 0-31
pid 705236's new affinity list: 2
pid 705237's current affinity list: 0-31
pid 705237's new affinity list: 0
pid 705238's current affinity list: 0-31
pid 705238's new affinity list: 2
pid 705239's current affinity list: 0-31
pid 705239's new affinity list: 0
当然,通过删除 cores_idx
参数,您仍然可以拥有 multiprocessing.Poll()
的常用功能。
我找到了一个不涉及更改 python 模块源代码的解决方案。它使用建议的方法 here。一个人只能检查 通过执行以下操作 运行 执行该脚本后物理核心处于活动状态:
lscpu
在bashreturns:
CPU(s): 8
On-line CPU(s) list: 0,2,4,6
Off-line CPU(s) list: 1,3,5,7
Thread(s) per core: 1
[可以从 python] 中 运行 上面链接的脚本。在任何情况下,在 运行 上面的脚本之后,在 python:
中输入这些命令import multiprocessing
multiprocessing.cpu_count()
returns 4.
我知道这个话题现在已经很老了,但是当在 google 中输入 'multiprocessing logical core' 时它仍然显示为第一个答案...我觉得我必须给出一个额外的答案,因为我看2018年(甚至更晚..)的人可能会在这里很容易混淆(有些答案确实有点混乱)
我认为没有比这里更好的地方来警告读者上面的一些答案了,很抱歉让这个话题复活。
--> 计算 CPU 秒 (LOGICAL/PHYSICAL) 使用 PSUTIL 模块
对于 ex 的 4 物理核心/8 线程 i7,它将 return
import psutil
psutil.cpu_count(logical = False)
4
psutil.cpu_count(logical = True)
8
就这么简单。
在那里,您不必担心 OS、平台、硬件本身或其他任何问题。 我相信它比 multiprocessing.cpu_count() 好得多,后者有时会给出奇怪的结果,至少从我自己的经验来看是这样。
--> 要使用 N 物理内核(由您选择)使用 YUGI
描述的多处理模块只要数一数你有多少个物理进程,启动一个 multiprocessing.Pool 4 个工人。
或者你也可以尝试使用joblib.Parallel()函数
2018 年的 joblib 不是 python 标准发行版的一部分,而只是 Yugi 描述的多处理模块的包装器。
--> MOST OF THE TIME,不要使用超过可用的核心(除非你已经对非常具体的代码进行了基准测试并证明它是值得的)
我们可以到处听到(也有一些人在这里回答)"the OS will take care properly is you use more core than available"。 绝对是 100% 假。如果您使用的核心多于可用的核心,您将面临巨大的性能下降。因为 OS 调度器会尽量以同样的注意力去处理每一个任务,有规律地从一个任务切换到另一个任务,并且取决于 OS,它最多可以花费 100% 的工作时间只是在进程之间切换,这将是灾难性的。
不要只相信我:尝试一下,对其进行基准测试,您会发现它有多清晰。
POS是否可以决定代码将在逻辑内核还是物理内核上执行?
如果你问这个问题,这意味着你不了解物理和逻辑核心的设计方式,所以也许你应该多检查一下处理器的架构。
例如,如果您想 运行 在核心 3 而不是核心 1 上,那么我想确实有一些解决方案,但只有当您知道如何编写 OS 的代码时才可用内核和调度程序,如果您问这个问题,我认为情况并非如此。
如果您在 4 个物理处理器/8 个逻辑处理器上启动 4 个 CPU-intensive 进程,调度程序会将您的每个进程分配给 1 个不同的物理核心(并且 4 个逻辑核心将保持 not/poorly 使用).但是在 4 逻辑/8 线程 proc 上,如果处理单元是 (0,1) (1,2) (2,3) (4,5) (5,6) (6,7),那么它不会进程在 0 或 1 上执行的区别:它是相同的处理单元。
至少据我所知(但专家可以确认/证实,也许它也不同于非常具体的硬件规格)我认为在 0 或 1 上执行代码之间没有或只有很小的区别。在处理中unit (0,1),我不确定 0 是逻辑值而 1 是物理值,还是 vice-versa。根据我的理解(这可能是错误的),两者都是来自同一个处理单元的处理器,它们只是共享它们的高速缓存/对硬件(包括 RAM)的访问,并且 0 不比 1 多一个物理单元。
除此之外,您应该让 OS 决定。因为 OS 调度程序可以利用某些平台(例如 i7、i5、i3...)上存在的硬件 logical-core 涡轮增压,所以您没有打开其他电源,这可能对你真正有帮助。
如果您在 4 个物理内核/8 个逻辑内核上启动 5 个 CPU-intensive 任务,行为将是混乱的,几乎不可预测,主要取决于您的硬件和 OS。调度程序将尽力而为。几乎每一次,你都将不得不面对非常糟糕的表现。
让我们暂时假设我们仍在谈论 4(8) 经典架构:因为调度程序会尽力而为(因此经常切换属性),根据您正在执行的进程,它可能是在 5 个逻辑内核上启动比在 8 个逻辑内核上启动更糟糕(至少他知道一切都会以 100% 的速度使用,所以迷路了他不会尝试避免它,不会切换得太频繁,并且因此不会因切换而浪费太多时间)。
但 99% 可以肯定(但要确定在您的硬件上进行基准测试)如果您使用的物理核心多于可用的物理核心,几乎所有多处理程序都会 运行 变慢。
很多事情都可以介入...程序,硬件,状态OS,它使用的调度程序,你今天早上吃的水果,你姐姐的名字......如果你对某些事情有疑问,只需对其进行基准测试,没有其他简单的方法可以看出你是否正在失去性能或不是。有时候信息学真的很奇怪。
--> MOST 的时间,额外的逻辑核心在 PYTHON 中确实无用(但并非总是如此)
在 python 中有 2 种主要方法可以执行真正的并行任务。
- 多处理(无法利用逻辑内核)
- 多线程(可以利用逻辑核心)
例如运行 4个并行任务
--> 多处理将创建 4 个不同的 python 解释器。对于它们中的每一个,你必须启动一个 python 解释器,定义 reading/writing 的权限,定义环境,分配大量内存等。让我们按原样说:你将启动一个整体从 0 开始的新程序实例。它可能需要大量时间,因此您必须确保这个新程序能够运行足够长的时间,这样它才值得。
如果你的程序有足够的工作(比如说,至少几秒钟的工作),那么因为 OS 在不同的物理核心上分配 CPU-consumming 进程,它可以工作,你可以获得了很多表演,这很棒。而且因为 OS 几乎总是允许进程在它们之间进行通信(尽管速度很慢),它们甚至可以交换(一点点)数据。
--> 多线程不同。在您的 python 解释器中,它只会创建少量内存,供许多 CPU 共享,并同时处理。它的生成速度要快得多(有时在旧计算机上生成一个新进程可能需要很多秒,而生成一个线程的时间却少得离谱)。您不会创建新进程,但 "threads" 会轻得多。
线程可以非常快速地在线程之间共享内存,因为它们确实在同一内存上一起工作(而在使用不同进程时必须 copied/exchanged)。
但是:为什么我们不能在 MOST 情况下使用多线程?看起来很方便?
在python中有一个非常大的限制:在python解释器中一次只能执行一行python,这被称为GIL(全局解释器锁).所以大多数时候,你甚至会通过使用多线程获得 LOSE 的性能,因为不同的线程将不得不等待访问同一个资源。如果您的代码是纯的 python.
,多线程总是无用甚至更糟--> 为什么我在使用多进程时不应该使用逻辑内核?
逻辑核心没有自己的内存访问。它们只能处理内存访问及其托管物理处理器的缓存。例如,同一处理单元的逻辑和物理核心很可能(并且经常使用)同时在高速缓存的不同位置上使用相同的 C/C++ 函数。确实使治疗速度大大加快。
但是...这些是 C/C++ 函数! Python 是一个很大的 C/C++ 包装器,它需要比等效的 C++ 代码更多的内存和 CPU。很可能在 2018 年,无论您想做什么,2 个大 python 进程需要的内存和缓存 reading/writing 远远超过单个物理+逻辑单元所能承受的,甚至更多等效的 C/C++ truly-multithreaded 代码会消耗什么。这又一次几乎总是会导致性能下降。请记住,处理器缓存中不可用的每个变量都将花费 x1000 倍的时间来读取内存。如果你的缓存对于 1 个 python 进程来说已经完全满了,猜猜如果你强制 2 个进程使用它会发生什么:他们将一次使用它,并永久切换,导致数据被愚蠢地刷新和re-read 每次切换。当数据正在从内存中读取或写入时,您可能认为您的 CPU "is" 正在工作,但事实并非如此。它在等待数据!什么都不做。
--> 那你怎么利用逻辑核心呢?
正如我所说,由于全局解释器锁,在默认 python 中没有真正的多线程(因此没有真正使用逻辑核心)。您可以在程序的某些部分强制删除 GIL,但我认为如果您不确切知道自己在做什么,最好不要碰它。
删除 GIL 无疑是很多研究的主题(请参阅尝试这样做的实验性 PyPy 或 Cython 项目)。
目前还没有真正的解决方案,因为它比看起来要复杂得多。
我承认,还有另一种可行的解决方案: - 用 C 语言编写你的函数 - 把它包起来python 使用 ctype - 使用 python 多线程模块调用包装的 C 函数
这将 100% 有效,您将能够使用 python 中的所有逻辑核心,并使用多线程,而且是真实的。 GIL 不会打扰您,因为您不会执行真正的 python 函数,而是执行 C 函数。
例如,像 Numpy 这样的一些库可以在所有可用的线程上工作,因为它们是用 C 编写的。但是如果你到了这一点,我一直认为考虑在 [=直接182=]++是因为考虑的很远,跟原来的pythonic精神相去甚远。
**--> 不要总是使用所有可用的物理内核 **
我经常看到人们像"Ok I have 8 physical core, so I will take 8 core for my job"。它通常有效,但有时会被证明是一个糟糕的主意,尤其是当您的工作需要大量 I/O 时。
尝试使用 N-1 个核心(再一次,特别是对于 I/O 要求很高的任务),您会发现在 per-task/average 上 100% 的时间,单个任务总是 运行 在 N-1 核心上更快。确实,您的计算机会产生很多不同的东西:USB、鼠标、键盘、网络、硬盘驱动器等......即使在工作站上,周期性任务也会随时在您不知道的后台执行。如果您不让 1 个物理核心来管理这些任务,您的计算将经常中断(从内存中清除/替换回内存),这也会导致性能问题。
你可能会想"Well, background tasks will use only 5% of CPU-time so there is 95% left"。但事实并非如此。
处理器一次处理一个任务。每次切换时,都会浪费大量时间将所有内容放回内存中的位置 cache/registries。然后,如果出于某种奇怪的原因,OS 调度程序过于频繁地进行此切换(这是您无法控制的),所有这些计算时间将永远丢失,您无能为力。
如果(有时会发生)由于某种未知原因,此调度程序问题影响的不是 1 个而是 30 个任务的性能,它可能会导致真正有趣的情况,在 29/30 物理内核上工作可能比在30/30
更多 CPU 并不总是最好的
当您使用 multiprocessing.Pool 时,经常会使用进程之间共享的 multiprocessing.Queue 或管理器队列,以允许它们之间进行一些基本通信。有时(我必须说 100 次,但我重复一遍),以 hardware-dependent 的方式,使用更多 CPU 使进程通信/同步时可能会造成瓶颈。在这些特定情况下,运行 在较低的 CPU 数字上可能会很有趣,或者甚至尝试在更快的处理器上驱逐同步任务(这里我说的是科学密集型计算 运行当然在集群上)。由于多处理通常用于集群,因此您必须注意,出于 energy-saving 目的,集群通常会降低频率。正因为如此,single-core 表现可能 真的 糟糕(由 way-much 更高数量的 CPU 平衡),使问题更糟您将代码从本地计算机(内核少,性能高 single-core)扩展到集群(内核多,性能低 single-core),因为根据 single_core_perf/nb_cpu 比率,您的代码瓶颈,有时真的很烦人
每个人都有尽可能多地使用 CPU 的诱惑。但这些案例的基准是强制性的。
典型情况(例如在数据科学中)是并行处理 N 个进程 运行,并且您想将结果汇总到一个文件中。因为您不能等待工作完成,所以您可以通过特定的编写程序来完成。作者将在他的 multiprocessing.Queue(single-core 和 hard-drive 有限进程)中推送的所有内容写入输出文件。 N个进程填充multiprocessing.Queue.
很容易想象,如果你有 31 CPU 向一个非常慢的 CPU 写入信息,那么你的性能将会下降(如果你克服了系统的能力,可能会出现崩溃)处理临时数据)
-->带回家留言
- 使用 psutil 来计算 logical/physical 个处理器,而不是 multiprocessing.cpu_count() 或任何
- 多处理只能在物理核心上工作(或者至少对其进行基准测试以证明它在您的情况下不是真的)
- 多线程将在逻辑核心上运行,但您必须用 C 编写代码并将您的函数包装起来,或者删除全局锁解释器(每次您这样做时,一只小猫都会在世界某个地方残忍地死去)
- 如果您尝试在纯 python 代码上 运行 多线程,您的性能会大幅下降,因此您应该在 99% 的时间使用多处理
- 除非你的processes/threads有很长时间您可以利用的原因,永远不要使用超过可用数量的核心,如果您想尝试,请正确进行基准测试
- 如果您的任务是 I/O 密集型任务,您应该让 1 个物理核心来处理 I/O,如果您有足够的物理核心,那将是值得的。对于多处理实现,它需要使用 N-1 个物理内核。对于经典的2路多线程来说,就是使用N-2个逻辑核心。
- 如果您需要更多性能,请尝试 PyPy(未准备好生产)或 Cython,甚至用 C 编写代码
最后但并非最不重要,也是最重要的一点:如果你真的追求性能,你应该绝对、始终、始终进行基准测试,而不是猜测任何东西。基准测试经常揭示 st运行ge platform/hardware/driver 您可能不知道的非常具体的行为。