为什么以下简单的并行化代码比Python中的简单循环慢得多?

时间:2017-10-13 09:47:27

标签: python arrays function parallel-processing

计算数字平方并存储结果的简单程序:

    import time
    from joblib import Parallel, delayed
    import multiprocessing

    array1 = [ 0 for i in range(100000) ]

    def myfun(i):
        return i**2

    #### Simple loop ####
    start_time = time.time()

    for i in range(100000):
        array1[i]=i**2

    print( "Time for simple loop         --- %s seconds ---" % (  time.time()
                                                               - start_time
                                                                 )
            )
    #### Parallelized loop ####
    start_time = time.time()
    results = Parallel( n_jobs  = -1,
                        verbose =  0,
                        backend = "threading"
                        )(
                        map( delayed( myfun ),
                             range( 100000 )
                             )
                        )
    print( "Time for parallelized method --- %s seconds ---" % (  time.time()
                                                               - start_time
                                                                 )
            )
    #### Output ####
    # >>> ( executing file "Test_vr20.py" )
    # Time for simple loop         --- 0.015599966049194336 seconds ---
    # Time for parallelized method --- 7.763299942016602 seconds ---

这两个选项的数组处理有何区别?我的实际程序会有更复杂的东西,但这是我需要尽可能简单地并行化的计算,但不是这样的结果。

System Model: HP ProBook 640 G2, Windows 7,
              IDLE for Python System Type: x64-based PC Processor:
              Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz,
              2401 MHz,
              2 Core(s),
              4 Logical Processor(s)

2 个答案:

答案 0 :(得分:4)

为什么?
因为在案件中尝试使用工具,所以工具主要不能使用,也不要调整进入成本:

我爱蟒蛇 我祈祷教育工作者更好地解释工具的成本,否则我们会迷失在这些希望获得的 [PARALLEL] - 计划中。

一些事实:

否。 0 :通过大量简化,python有意使用 GIL [SERIAL] - 访问变量和从而避免了[CONCURRENT]修改带来的任何潜在碰撞 - 在额外的时间内支付GIL阶梯式舞蹈的附加费用 不。 1 [PARALLEL] - 代码执行比"只是" - [CONCURRENT]read more)更难 不。 2 [SERIAL] - 如果尝试将工作拆分为[CONCURRENT],则流程必须支付额外费用 - 工人 否。 3 :如果进程进行员工间通信,则每次数据交换需要支付巨额额外费用 不。 4 :如果硬件的[CONCURRENT]进程资源很少,则结果会进一步恶化

要了解标准python 2.7.13中可以做些什么:

效率是更好地使用硅,而不是推动语法构造器进入领域,它们是合法的,但它们的性能对被测实验的端到端速度有不利影响:

您支付 8 ~ 11 [ms] 只是为了反复汇编空 array1

>>> from zmq import Stopwatch
>>> aClk = Stopwatch()
>>> aClk.start();array1 = [ 0 for i in xrange( 100000 ) ];aClk.stop()
 9751L
10146L
10625L
 9942L
10346L
 9359L
10473L
 9171L
 8328L

Stopwatch().stop()方法从[us]产生 .start()
而内存效率,可矢量化,无GIL方法可以做到相同大约+ 230x~ + 450x更快:

>>> import numpy as np
>>>
>>> aClk.start();arrayNP = np.zeros( 100000 );aClk.stop()
   15L
   22L
   21L
   23L
   19L
   22L

>>> aClk.start();arrayNP = np.zeros( 100000, dtype = np.int );aClk.stop()
   43L
   47L
   42L
   44L
   47L

因此,使用适当的工具只会启动性能故事:

>>> def test_SERIAL_python( nLOOPs = 100000 ):
...     aClk.start()
...     for i in xrange( nLOOPs ):           # py3 range() ~ xrange() in py27 
...         array1[i] = i**2                 # your loop-code
...     _ = aClk.stop()
...     return _

虽然天真的 [SERIAL] -iterative实施有效,但您需要支付巨额费用才能选择 ~ 70 [ms] 获取100000-D矢量:

>>> test_SERIAL_python( nLOOPs = 100000 )
 70318L
 69211L
 77825L
 70943L
 74834L
 73079L

使用更合适/合适的工具仅需约0.2 [ms] 即。 ++ 350x更快

>>> aClk.start();arrayNP[:] = arrayNP[:]**2;aClk.stop()
189L
171L
173L
187L
183L
188L
193L

并且另一个小故障,a.k.a。一个地方运作方式:

>>> aClk.start();arrayNP[:] *=arrayNP[:];aClk.stop()
138L
139L
136L
137L
136L
136L
137L

仅使用适当的工具

,产生〜+ 514x SPEEDUP

表现艺术并非遵循以下有关并行化 - ( at-any-cost 的营销声明,但在使用基于技术诀窍的方法,为可实现的最大加速成本支付最低成本。

对于&#34;小&#34; - 问题,分发&#34;瘦&#34; -work-packages的典型成本确实难以被任何可能实现的加速所覆盖,因此&#34;问题 - 大小&#34;实际上限制了一种方法的选择,可以达到正增益(这里经常报告加速度为0.9或甚至<&lt; 1.0),在StackOverflow上,您不需要感到丢失或单独使用惊讶)。

后记

处理器编号计数 核心数量 但缓存大小+ NUMA不规则的数量不止于此 智能,矢量化,HPC固化,无GIL的库很重要( numpy 等等 - 非常感谢Travis OLIPHANT&amp; al ...给他的团队致敬。 ..)

As an overhead-strict Amdahl Law (re-)-formulation explains为什么甚至很多 - N - CPU 并行化代码执行可能(实际上经常)受到加速&LT;&LT; 1.0

Amdahl法律加速 S 的严格制定包括付费[PAR]-设置+ [PAR]-终止开销的成本,的明确地

               1
S =  __________________________; where s, ( 1 - s ), N were defined above
                ( 1 - s )            pSO:= [PAR]-Setup-Overhead     add-on
     s  + pSO + _________ + pTO      pTO:= [PAR]-Terminate-Overhead add-on
                    N               

(这些性能限制的an interactive animated tool for 2D visualising effects被引用here

答案 1 :(得分:3)

来自threading的{​​{3}}:

  

如果你知道你正在调用的函数是基于编译的   发布Python全局解释器锁(GIL)的扩展   在大多数计算过程中......

问题在于,在这种情况下,你不知道。 Python本身只允许一个线程一次运行(python解释器每次执行python操作时都会锁定GIL。)

threading仅在myfun()大部分时间花在已编译的Python扩展程序上且该扩展程序发布GIL 时才有用。

Parallel代码是如此令人尴尬地缓慢,因为你正在做大量的工作来创建多个线程 - 然后你只能一次执行一个线程。

如果使用multiprocessing后端,则必须将输入数据复制到四个或八个进程(每个核心一个)中的每个进程中,在每个进程中执行处理,然后将输出数据复制回来。复制速度会很慢,但如果处理比计算正方形稍微复杂一些,那么它可能是值得的。测量并查看。