这两个函数非常不同,
NUM_WORKERS = os.sched_getaffinity(0) - 1
将无法立即成功,因为您尝试从一个集合中减去一个整数会导致
TypeError
。而
os.cpu_count()
告诉您系统有多少个内核,
os.sched_getaffinity(pid)
告诉您哪些线程/进程可以在哪些内核上运行。
os.cpu_count()
os.cpu_count()
显示操作系统中可用内核的数量(虚拟内核)。很可能你只有一半的物理内核。是否有意义使用比你实际拥有的物理内核更多的进程,甚至是超过虚拟内核数量,这通常取决于你所做的工作。计算循环越紧密(指令少,缓存未命中少等),使用更多的核心(通过使用更多的worker-process)甚至会出现性能下降。
显然,这也取决于您的系统正在运行什么其他程序,因为您的系统会尝试为系统中的每个线程(作为进程的实际执行单元)提供公平份额的运行时间。因此,无法就使用多少worker进行概括。但是,例如,如果您有一个紧密的循环并且您的系统处于空闲状态,则优化的良好起点为
os.cpu_count() // 2 # same as mp.cpu_count() // 2
...然后逐步增加。
正如@Frank Yellin所提到的,默认情况下multiprocessing.Pool
使用os.cpu_count()
作为工作进程数量。
os.sched_getaffinity(pid)
os.sched_getaffinity(pid)
返回PID为pid的进程(如果为零,则返回当前进程)限制的CPU集合。
现在,核心/CPU/处理器亲和性是关于您的线程(在您的工作进程内部)被允许运行的具体(虚拟)核心。您的操作系统为每个核心分配一个ID,从0到(核心数-1),更改亲和性允许限制(“固定”)某个线程在哪个实际核心上可以运行。
至少在Linux上,我发现这意味着如果没有可用的允许核心,子进程的线程将不会运行,即使其他未被允许的核心为空闲状态。因此,“亲和性”在这里有点误导。
调整亲和性时的目标是最小化因上下文切换和核心迁移而导致的缓存失效。您的操作系统通常具有更好的洞察力,并已经尝试使用其调度策略保持缓存“热”,因此除非您知道自己在做什么,否则不能轻易地干预。
默认情况下,亲和性设置为所有核心,对于multiprocessing.Pool
而言,如果您的系统除此之外处于空闲状态,更改这一点并没有太大意义。
请注意,尽管这里的文档中提到了“进程”,但实际上设置亲和性是针对每个线程的。因此,例如,在“子”线程中为“零号进程”设置亲和性不会更改主线程或进程中的其他线程的亲和性。但是,子线程从主线程继承其亲和性,子进程(通过其主线程)从父进程的主线程继承亲和性。这会影响所有可能的启动方法(“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)):
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")
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))
输出:
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}
os.cpu_count()
). 性能提升是因为被创建的进程在等待 URL 返回时会放弃它们所拥有的核心 (无论是真实还是虚拟的),如果有其他进程在等待核心运行,它现在就能得到机会。 - Booboo