5

我正在尝试在 python 下运行并行进程(在 ubuntu 上)。

我开始使用多处理,它适用于简单的示例。
然后出现了泡菜错误,所以我切换到悲情。我对不同的选项有点困惑,所以写了一个非常简单的基准测试代码。

import multiprocessing as mp
from pathos.multiprocessing import Pool as Pool1
from pathos.pools import ParallelPool as Pool2
from pathos.parallel import ParallelPool as Pool3
import time

def square(x):  
    # calculate the square of the value of x
    return x*x

if __name__ == '__main__':

    dataset = range(0,10000)

    start_time = time.time()
    for d in dataset:
        square(d)
    print('test with no cores: %s seconds' %(time.time() - start_time))

    nCores = 3
    print('number of cores used: %s' %(nCores))  


    start_time = time.time()

    p = mp.Pool(nCores)
    p.map(square, dataset)

    # Close
    p.close()
    p.join()

    print('test with multiprocessing: %s seconds' %(time.time() - start_time))


    start_time = time.time()

    p = Pool1(nCores)
    p.map(square, dataset)

    # Close
    p.close()
    p.join()

    print('test with pathos multiprocessing: %s seconds' %(time.time() - start_time))


    start_time = time.time()

    p = Pool2(nCores)
    p.map(square, dataset)

    # Close
    p.close()
    p.join()

    print('test with pathos pools: %s seconds' %(time.time() - start_time))


    start_time = time.time()

    p = Pool3()
    p.ncpus = nCores
    p.map(square, dataset)

    # Close
    p.close()
    p.join()

    print('test with pathos parallel: %s seconds' %(time.time() - start_time))

我得到大约
- 0.001s 使用纯串行代码,没有并行,
- 0.100s 使用multiprocessing选项,
- 0.100s 使用pathos.multiprocessing
- 4.470s 使用pathos.pools
-AssertionError错误pathos.parallel

我从http://trac.mystic.cacr.caltech.edu/project/pathos/browser/pathos/examples.html复制了如何使用这些不同的选项

我知道对于这样一个简单的示例,并行处理比普通的串行代码要长。我不明白的是悲情的相对表现。

我检查了讨论,但不明白为什么pathos.pools会这么长,以及为什么会出错(不确定最后一个选项的性能如何)。

我还尝试了一个简单的平方函数,甚至pathos.multiprocessingmultiprocessing

有人可以解释这些不同选项之间的区别吗?

此外,我在远程计算机上运行该选项,运行centOS pathos.multiprocessing,性能multiprocessing.

根据租用计算机的公司的说法,它应该像家用计算机一样工作。我知道,如果没有更多关于机器的详细信息,可能很难提供信息,但如果您对它的来源有任何想法,那将有所帮助。

4

2 回答 2

12

我是pathos作者。对困惑感到抱歉。您正在处理新旧编程接口的混合。

“新”(建议)界面是使用pathos.pools. 旧界面链接到相同的对象,因此实际上有两种方法可以访问相同的东西。

multiprocess.Pool是 的一个分支multiprocessing.Pool,唯一的区别是multiprocessing使用picklemultiprocess使用dill。所以,我希望在最简单的情况下速度是一样的。

上述游泳池也可以在pathos.pools._ProcessPool. pathos为几种类型的池提供了一个小型包装器,具有不同的后端,提供了扩展的功能。-wrappedpathos池是pathos.pools.ProcessPool(旧接口在 处提供它pathos.multiprocessing.Pool)。

首选界面是pathos.pools.ProcessPool.

还有ParallelPool, 它使用不同的后端——它使用ppft而不是multiprocess. ppft是“并行 python”,它通过源代码生成 python 进程subprocess并传递源代码(dill.source而不是序列化对象)——它用于分布式计算,或者当通过源代码传递时是更好的选择。

因此,pathos.pools.ParallelPool是首选接口,并且pathos.parallel.ParallelPool(以及 中的其他一些类似引用pathos)由于遗留原因而存在 - 但它们是下面的同一个对象。

总之:

>>> import multiprocessing as mp
>>> mp.Pool()
<multiprocessing.pool.Pool object at 0x10fa6b6d0>
>>> import multiprocess as mp
>>> mp.Pool()
<multiprocess.pool.Pool object at 0x11000c910>
>>> import pathos as pa
>>> pa.pools._ProcessPool()
<multiprocess.pool.Pool object at 0x11008b0d0>
>>> pa.multiprocessing.Pool()
<multiprocess.pool.Pool object at 0x11008bb10>
>>> pa.pools.ProcessPool()
<pool ProcessPool(ncpus=4)>
>>> pa.pools.ParallelPool()
<pool ParallelPool(ncpus=*, servers=None)>

您可以看到ParallelPoolhas servers... 因此用于分布式计算。

剩下的唯一问题是为什么AssertionError?那是因为pathos添加的包装器保留了一个池对象以供重用。因此,当您ParallelPool第二次调用时,您调用的是一个封闭池。您需要到restart池中才能再次使用它。

>>> f = lambda x:x
>>> p = pa.pools.ParallelPool()
>>> p.map(f, [1,2,3])
[1, 2, 3]
>>> p.close()
>>> p.join()
>>> p.restart()  # throws AssertionError w/o this
>>> p.map(f, [1,2,3])
[1, 2, 3]
>>> p.close()
>>> p.join()
>>> p.clear()  # destroy the saved pool

ProcessPool具有相同的界面ParallelPool,关于重启和清除已保存的实例。

于 2018-04-09T15:52:08.250 回答
2

有人可以解释这些差异吗?

让我们从一些共同点开始。

作为标准,Python 解释器使用 GIL 步进代码执行。这意味着,所有基于线程的池仍会等待所有代码执行路径的 GIL 步进排序,因此任何此类构建的尝试都不会享受理论上预期的”好处。

Python 解释器可以使用其他基于进程的实例来加载多个进程,每个进程都有自己的 GIL 锁,形成多个并发代码执行路径的池。

在处理了这个主要的消歧之后,接下来开始出现与性能相关的问题。最负责任的方法是基准,基准,基准。这里也不例外。


花了这么多时间在这里(哪里)?

主要(常量)部分是[TIME]流程实例化的主要域成本。在这里,python 解释器的完整副本,包括所有变量、所有内存映射,实际上是调用 python 解释器的完整状态完整副本,必须首先创建并放置到操作系统进程调度程序表中,然后再进一步(工作的有用部分)可以在“内部”计算这种成功实例化的子流程。如果您的有效载荷函数立即从那里返回,并创建了一个x*x,那么您的代码似乎已经为一些 CPU 指令消耗了所有燃料,并且您花费的回报比收到的要多得多。成本经济对您不利,因为所有进程实例化加上进程终止成本都远高于几个 CPU-CLOCK 滴答声。

这实际上需要多长时间?
您可以对此进行基准测试(如此处所提议,在提议中Test-Case-A。如果Stopwatch()-ed[us]决定,您将开始更多地依赖事实,而不是任何类型的崇拜大师或营销类型的建议。这很公平,不是吗?)。


Test-Case-A基准流程实例化成本 [MEASURED]。
接下来是什么?

下一个最危险的(大小可变)部分主要是[SPACE]域成本,但如果分配成本开始超过小规模规模,也会产生[TIME]域影响。[SPACE]

这种附加开销成本与将“大”大小的参数从“主”-python 解释器传递到每个(分布式)子流程实例的任何需要相关。

这需要多长时间?
再次,基准,基准,基准。应对此进行基准测试(如建议的here,如果扩展一个建议用一些确实“胖”的数据块Test-Case-C替换参数,无论是一个或其他类型,承载一些巨大的内存占用。)aNeverConsumedPARnumpy.ndarray()

这样,真正的硬件相关 + O/S 相关 + python 相关的数据流成本开始变得可见并在这样的基准中衡量,例如**[us]**. 这对老黑客来说并不是什么新鲜事,然而,那些从未遇到过 HDD 磁盘写入时间可能会增长并阻塞其他处理数秒或数分钟的人几乎不会相信,如果不通过自己的基准测试来触及真正的成本数据流。所以,不要犹豫,将基准扩展Test-Case-C到确实很大的内存足迹以闻烟味......


最后但并非最不重要的一点是,重新制定的阿姆达尔定律将告诉...

鉴于尝试并行化某些计算,无论是根据计算部分还是根据所有开销部分,都可以很好地理解,图片开始变得完整:

开销严格和资源感知的阿姆达尔定律重新制定表明:

                           1                         
S =  ______________________________________________ ;  where         s,
                    /                     \                    ( 1 - s ),
                   |  ( 1 - s )            |                       pSO,
     s  + pSO + max|  _________ , atomicP  |  + pTO                pTO,
                   |      N                |                         N
                    \                     /           have been defined in
                                                      just an Overhead-strict Law
and
atomicP := is a further indivisible duration of an atomic-process-block

所产生的加速S总是会遭受高昂的开销成本pSO + pTO,就像任何高的N都不允许进一步帮助时一样,因为 的值足够高atomicP

在所有这些情况下,最终的加速S可能很容易落入<< 1.0,是的,完全在纯[SERIAL]代码执行路径计划下(再次,对pSOpTO(的实际成本进行了基准测试,测试用例-A + 测试用例-C(扩展) 被示意性地提出) 有机会得出所需的最小合理计算负载,以保持在加速的神秘水平之上>= 1.0

于 2018-02-26T15:15:33.233 回答