我有一个函数foo
,它消耗大量内存,我希望并行运行多个实例。
假设我有一个带有4个物理内核的CPU,每个内核都有两个逻辑内核。
我的系统有足够的内存可以并行容纳4个foo
个实例但不是8个。而且,由于这8个内核中有4个是逻辑的,所以我也不希望使用所有8个内核会提供很多收益超出使用4个物理的。
所以我想在4个物理内核上运行foo
。换句话说,我想确保执行multiprocessing.Pool(4)
(4是由于内存限制,我可以在此计算机上容纳的函数的最大并发运行次数)将作业调度到四个物理核心(而不是例如,两个物理核心及其两个逻辑后代的组合。
如何在python中执行此操作?
我之前使用过multiprocessing
的代码示例,但我与库无关,所以为了避免混淆,我删除了它。
答案 0 :(得分:11)
注意:这种方法不适用于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,第二个进程默认分配给core0,然后其亲和力更改为下一个核(1),依此类推。
使用multiprocessing.Pool
:
警告:此方法需要调整pool.py
模块,因为我无法知道您可以从Pool()
中提取pid。此更改也已在python 2.7
和multiprocessing.__version__ = '0.70a1'
上进行了测试。
在Pool.py
中,找到调用_task_handler_start()
方法的行。在下一行中,您可以将池中的流程分配给每个" physical"核心使用(我把import os
放在这里,以便读者不会忘记导入它):
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-cores,您最终会在同一核心上拥有多个作业。
编辑:
OP正在寻找的是pool()
能够在特定核心上盯着池。为此,需要对multiprocessing
进行更多调整(首先撤消上述更改)。
警告:
不要尝试复制粘贴函数定义和函数调用。仅复制粘贴self._worker_handler.start()
之后应添加的部分(您将在下面看到它)。请注意,我的multiprocessing.__version__
告诉我版本为'0.70a1'
,但只要您添加需要添加的内容,它就无关紧要了:
multiprocessing
' pool.py
:
为cores_idx = None
定义添加__init__()
参数。在我的版本中,添加后它看起来像这样:
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()
参数以及在返回部分中添加另一个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
当然,通过删除multiprocessing.Poll()
参数,您仍然可以拥有cores_idx
的常用功能。
答案 1 :(得分:2)