这两个功能有很大不同NUM_WORKERS = os.sched_getaffinity(0) - 1
会立即失败TypeError
因为您尝试从集合中减去一个整数。尽管os.cpu_count()
告诉您系统有多少个核心,os.sched_getaffinity(pid)
告诉您某个线程/进程位于哪些核心上allowed to run.
os.cpu_count()
os.cpu_count()显示操作系统已知的可用核心数量(virtual核心)。您很可能拥有这个数量的一半physical核心。使用比物理核心更多的进程,甚至比虚拟核心更多的进程是否有意义,很大程度上取决于您正在做什么。计算循环越紧密(指令多样性很少,缓存未命中次数很少,...),您就越有可能无法从更多使用的核心(通过使用更多工作进程)中受益,甚至会遇到性能下降。
显然,这还取决于您的系统正在运行什么,因为您的系统试图为系统中的每个线程(作为进程的实际执行单元)提供可用内核上公平的运行时间份额。因此,就您有多少工人而言,不可能一概而论。should使用。但是,例如,如果您有一个紧密的循环并且您的系统处于空闲状态,那么优化的一个很好的起点是
os.cpu_count() // 2 # same as mp.cpu_count() // 2
...并从那里开始增加。
@Frank Yellin 已经提到过,multiprocessing.Pool
uses os.cpu_count()
为默认的工人数量。
os.sched_getaffinity(pid)
os.sched_getaffinity(pid)
返回具有 PID pid 的进程的 CPU 集(或当前
过程(如果为零)被限制为。
现在 core/cpu/processor/-affinity 是关于您的线程(在工作进程内)位于哪个具体(虚拟)核心上allowed跑步。您的操作系统为每个核心提供一个 id,从 0 到(核心数 - 1),并且更改关联性可以限制(“固定”)某个线程允许在哪些实际核心上运行。
至少在 Linux 上,我发现这意味着如果当前没有允许的核心可用,则子进程的线程将不会运行,即使其他不允许的核心处于空闲状态。所以“亲和力”在这里有点误导。
摆弄关联性时的目标是最大限度地减少上下文切换和核心迁移造成的缓存失效。您的操作系统通常具有更好的洞察力,并且已经尝试通过其调度策略使缓存保持“热”状态,因此除非您知道自己在做什么,否则您不能指望通过干扰轻松获得收益。
默认情况下,关联性设置为所有核心,并且multiprocessing.Pool
,更改它没有太大意义,至少在您的系统处于空闲状态时是这样。
请注意,尽管这里的文档谈到了“进程”,但设置亲和力实际上是每个线程的事情。因此,例如,在“子”线程中为“当前进程(如果为零)”设置亲和力,不会更改主线程或进程内其他线程的亲和力。But,子线程从主线程继承其亲和力,子进程(通过其主线程)从父进程的主线程继承亲和力。这会影响所有可能的启动方法(“spawn”、“fork”、“forkserver”)。下面的示例演示了这一点以及如何使用修改亲和力multiprocessing.Pool
.
import multiprocessing as mp
import threading
import os
def _location():
return f"{mp.current_process().name} {threading.current_thread().name}"
def thread_foo():
print(f"{_location()}, affinity before change: {os.sched_getaffinity(0)}")
os.sched_setaffinity(0, {4})
print(f"{_location()}, affinity after change: {os.sched_getaffinity(0)}")
def foo(_, iterations=200e6):
print(f"{_location()}, affinity before thread_foo:"
f" {os.sched_getaffinity(0)}")
for _ in range(int(iterations)): # some dummy computation
pass
t = threading.Thread(target=thread_foo)
t.start()
t.join()
print(f"{_location()}, affinity before exit is unchanged: "
f"{os.sched_getaffinity(0)}")
return _
if __name__ == '__main__':
mp.set_start_method("spawn") # alternatives on Unix: "fork", "forkserver"
# for current process, exclude cores 0,1 from affinity-mask
print(f"parent affinity before change: {os.sched_getaffinity(0)}")
excluded_cores = {0, 1}
os.sched_setaffinity(0, os.sched_getaffinity(0).difference(excluded_cores))
print(f"parent affinity after change: {os.sched_getaffinity(0)}")
with mp.Pool(2) as pool:
pool.map(foo, range(5))
Output:
parent affinity before change: {0, 1, 2, 3, 4, 5, 6, 7}
parent affinity after change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 MainThread, affinity before thread_foo: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 MainThread, affinity before thread_foo: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 Thread-1, affinity before change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 Thread-1, affinity after change: {4}
SpawnPoolWorker-1 MainThread, affinity before exit is unchanged: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 MainThread, affinity before thread_foo: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-1, affinity before change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-1, affinity after change: {4}
SpawnPoolWorker-2 MainThread, affinity before exit is unchanged: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 MainThread, affinity before thread_foo: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-2, affinity before change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-2, affinity after change: {4}
SpawnPoolWorker-2 MainThread, affinity before exit is unchanged: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 MainThread, affinity before thread_foo: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 Thread-2, affinity before change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-1 Thread-2, affinity after change: {4}
SpawnPoolWorker-1 MainThread, affinity before exit is unchanged: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-3, affinity before change: {2, 3, 4, 5, 6, 7}
SpawnPoolWorker-2 Thread-3, affinity after change: {4}
SpawnPoolWorker-2 MainThread, affinity before exit is unchanged: {2, 3, 4, 5, 6, 7}