Python multiprocessing.Pool忽略类方法

时间:2015-04-23 07:19:38

标签: python python-2.7 multiprocessing python-multiprocessing pathos

我最近为我的研究编写了一个课程,我试图将其并行化。当我使用Python 2.7的多处理。使用JoinableQueue和托管数据进行处理时,我的程序最终会以失效的进程挂起。

import multiprocessing as mp
import traceback

class Paramfit(object):
    def __init__(self):
        pass

    def _calc_bond(self, index):
        # Calculate data

    def _calc_parallel(self, index):
        self._calc_bond(index)

    def run(self):
        for ts, force in itertools.izip(self.coortrj, self.forcevec):
        try:
            consumers = [mp.Process(target=self._calc_parallel,
                         args=(force,)) for i in range(nprocs)]
            for w in consumers:
                w.start()

            # Enqueue jobs
            for i in range(self.totalsites):
                self.tasks.put(i)

            # Add a poison pill for each consumer
            for i in range(nprocs):
                self.tasks.put(None)

            self.tasks.close()
            self.tasks.join()

    #       for w in consumers:
    #           w.join()
        except:
            traceback.print_exc()

_calc_parallel调用其他一些类方法。

我甚至尝试使用multiprocessing.Pool,使用http://bytes.com/topic/python/answers/552476-why-cant-you-pickle-instancemethods上其他位置找到的copy_reg选项。

import multiprocessing as mp
import traceback

class Paramfit(object):
    def __init__(self):
        pass

    def _calc_bond(self, index):
        # Calculate data

    def _use_force(force):
        # Calculate data

    def _calc_parallel(self, index, force):
        self._calc_bond(index)
        self._use_force(force)

    def run(self):
        try:
            pool = mp.Pool(processes=nprocs, maxtasksperchild=2)
            args = itertools.izip(range(self.totalsites), itertools.repeat(force))
            pool.map_async(self._calc_parallel, args)
            pool.close()
            pool.join()
        except:
            traceback.print_exc()

但是,pool.map_async似乎不会调用self._calc_parallel。我知道在这两种情况下(进程和池),我忽略了一些东西,但我并不清楚什么。我通常处理超过40,000个元素。

感谢您的帮助。

更新

在阅读了其他几篇文章后,我也试过了pathos.multiprocessing。

import pathos.multiprocessing as mp
class Paramfit(object):
    def __init__(self):
        pass

    def _calc_bond(self, index):
        # Calculate data

    def _use_force(force):
        # Calculate data

    def _calc_parallel(self, index, force):
        self._calc_bond(index)
        self._use_force(force)

    def run(self):
        try:
            pool = mp.ProcessingPool(nprocs)
            args = itertools.izip(range(self.totalsites), itertools.repeat(force))
            pool.amap(lambda x: self._calc_parallel(*x), args)
        except:
            traceback.print_exc()

而且,和我之前的尝试一样,这似乎也很快就没有调用方法。

更新2

我决定改进代码,将我的庞然大物分成更小,更易于管理的组件。但是,如果我使用pathos.multiprocessing,我会遇到与之前发布的不同的情况(请参阅link)。我的新代码现在有一个可用于计算的对象,然后通过其方法返回一个值。

import itertools
import pandas as pd
import pathos.multiprocessing as mp

class ForceData(object):
    def __init__(self, *args, **kwargs):
        # Setup data
        self.value = pd.DataFrame()
    def calculateBondData(self, index):
        # Calculation
        return self.value
    def calculateNonBondedData(self, index):
        # Calculation
        return self.value
    def calculateAll(self, index):
        # Because self.value is a pandas.DataFrame, changed internally
        self.calculateBondData(index)
        self.calculateNonBondedData(index)
        return self.value

class ForceMatrix(object):
    def __init__(self, *args, **kwargs):
        # Initialize data
        self._matrix = pd.DataFrame()
    def map(self, data):
        for value in data.get():
            for i, j in itertools.product(value.index, repeat=2):
                self._matrix.loc[[i], [j]] += value.values

def calculate(self, *args, **kwargs):
    # Setup initial information.
    fd = ForceData()
    matrix = ForceMatrix()
    pool = mp.ProcessingPool()
    data = pool.amap(fd.calculateAll, range(x))
    matrix.map(data, force)
    return matrix

我认为这是一个单独的函数func(dataobj, force),但这似乎也无济于事。按照目前的速度,我估计单个处理器上的完整计算需要花费超过1000小时,这对于应该更快的东西来说太长了。

更新3(2015年4月30日)

由于@MikeMcKerns有用的见解,我可能已经找到了可能的解决方案。在群集的iMac(四核)或16核节点上,我发现,对于没有绑定的粗粒度(CG)系统,双itertools.imap似乎是我最好的解决方案( 1000个CG站点)每个轨迹帧大约5.2秒。当我进入一个包含一些债券细节的系统(3000个CG网站代表水)时,我发现,在iMac上(使用1个核心),itertools.imap后跟pathos.ThreadingPool.uimap个(4个线程)时钟大约85秒/帧;如果我按照@MikeMcKerns的评论中的建议尝试进程池(4核x 2)/线程池(4个线程),则计算时间增加2.5倍。在16核集群(32 pp / 16 tp)上,这个CG系统也很慢(大约160 s /帧)。在iMac(1个核心/ 4个线程)上拥有42,778个站点和众多绑定的CG系统可以在大约58分钟/帧内计时。我还没有在集群的16核节点上测试这个大型系统,但是我不确定是否使用进程池/线程池来加快它的速度。

示例:

# For a CG system with no bond details
for i in range(nframes):
    data1 = itertools.imap(func1, range(nsites))
    data2 = itertools.imap(func2, data1)
    for values in data2:
        func3(values)

# For a system with bond details
import pathos.multiprocessing as mp

tpool = mp.ThreadingPool(mp.cpu_count())
for i in range(nframes):
    data1 = itertools.imap(func1, range(nsites))
    data2 = tpool.uimap(func2, data1)
    for values in data2:
        func3(values)

# Seems to be the slowest in the bunch on iMac and possibly on 16-cores of a node.
ppool = mp.ProcessingPool(mp.cpu_count() * 2)
tpool = mp.ThreadingPool(mp.cpu_count())
for i in range(nframes):
    data1 = ppool.uimap(func1, range(nsites))
    data2 = tpool.uimap(func2, data1)
    for values in data2:
        func3(values)

我怀疑系统越大,我从多处理中获得的好处就越大。我知道大型CG系统(42,778个站点)需要大约0.08秒/站点,而0.02秒/站点(3000个CG站点)或0.05秒/站点(1000个站点没有绑定)。

在我努力削减计算时间的过程中,我发现了可以减少一些计算的区域(例如,global变量和算法更改),但如果我可以进一步减少这一点 - 规模多精度,这将是伟大的。

1 个答案:

答案 0 :(得分:2)

如果您使用的是python 2.7

,那么您的选项相当有限

这是一个使用池参数调用对象方法的简短示例。

第一个问题是只能在模块的顶层定义的功能被腌制。基于Unix的系统有办法克服这个限制,但不应该依赖它。所以你必须定义一个函数来获取你想要的对象和调用相关方法所需的参数。

例如:

def do_square(args):
    squarer, x = args # unpack args
    return squarer.square(x)

Squarer类可能如下所示:

class Squarer(object):
    def square(self, x):
        return x * x

现在并行应用square函数。

if __name__ == "__main__":
    # all pool work must be done inside the if statement otherwise a recursive 
    # cycle of Pool spawning will be created.

    pool = Pool()
    sq = Squarer()
    # create args as a sequence of tuples
    args = [(sq, i) for i in range(10)]
    print pool.map(do_square, args)

    pool.close()
    pool.join()

请注意,平方器不是有状态的。它接收数据,处理数据并返回结果。这是因为孩子的状态与父母的状态是分开的。除非使用多处理提供的队列,管道或其他共享状态类,否则一个中的更改不会反映在另一个中。通常,最好从子进程返回计算数据,然后在父进程中对该数据执行某些操作,而不是尝试将数据存储在子进程可以访问的某些共享变量中。

有状态平方器不使用多处理的示例:

class StatefulSquarer(object):

    def __init__(self):
        self.results = []

    def square(self, x):
        self.results.append(x * x)

if __name__ == "__main__":

    print("without pool")
    sq = StatefulSquarer()
    map(do_square, [(sq, i) for i in range(10)])
    print(sq.results)

    print("with pool")
    pool = Pool()
    sq = StatefulSquarer()
    pool.map(do_square, [(sq, i) for i in range(10)])
    print(sq.results)

    pool.close()
    pool.join()

如果你想做这项工作,那么更好的解决方案就是:

for result in pool.map(do_square, [(sq, i) for i in range(10)]):
    sq.results.append(result)

如果你的班级很大,但是不可改变怎么办?每次在map中开始一个新任务时,都必须将这个巨大的对象复制到子进程。但是,您可以通过仅将其复制到子进程一次来节省时间。

from multiprocessing import Pool

def child_init(sq_):
    global sq
    sq = sq_

def do_square(x):
    return sq.square(x)

class Squarer(object):
    def square(self, x):
        return x * x

if __name__ == "__main__":
    sq = Squarer()
    pool = Pool(initializer=child_init, initargs=(sq,))

    print(pool.map(do_square, range(10)))

    pool.close()
    pool.join()