多处理:仅使用物理核心?

时间:2016-10-24 12:01:06

标签: python linux parallel-processing python-multiprocessing

我有一个函数foo,它消耗大量内存,我希望并行运行多个实例。

假设我有一个带有4个物理内核的CPU,每个内核都有两个逻辑内核。

我的系统有足够的内存可以并行容纳4个foo个实例但不是8个。而且,由于这8个内核中有4个是逻辑的,所以我也不希望使用所有8个内核会提供很多收益超出使用4个物理的。

所以我想在4个物理内核上运行foo 。换句话说,我想确保执行multiprocessing.Pool(4)(4是由于内存限制,我可以在此计算机上容纳的函数的最大并发运行次数)将作业调度到四个物理核心(而不是例如,两个物理核心及其两个逻辑后代的组合。

如何在python中执行此操作?

编辑:

我之前使用过multiprocessing的代码示例,但我与库无关,所以为了避免混淆,我删除了它。

2 个答案:

答案 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.7multiprocessing.__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)

我找到了一个不涉及更改python模块源代码的解决方案。它使用建议的方法here。人们只能检查一下 运行该脚本后,物理核心处于活动状态:

lscpu
bash中的

返回:

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()

返回4.