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

问题描述 投票:0回答:2

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

    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)
python arrays function parallel-processing
2个回答
5
投票

为什么?因为尝试在以下情况下使用工具,
工具基本上不能也不会调整进入成本:

我喜欢Python。
我祈祷教育工作者更好地解释工具的成本,否则我们会迷失在这些想要得到的

[PARALLEL]
-时间表中。

一些事实:

No.0:通过大量简化,Python 有意使用 GIL

[SERIAL]
访问变量,从而避免
[CONCURRENT]
修改带来的任何潜在冲突 - 支付这些 GIL 的附加成本-加时赛中的踢踏舞
否。1
[PARALLEL]
-代码执行比“只是”困难得多-
[CONCURRENT]
阅读更多
No.2
[SERIAL]
-如果试图将工作分配给
[CONCURRENT]
-工人,流程必须支付额外费用 No.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 的方法也可以做到同样的事情,速度快了 230 倍 ~ 450 倍:

>>> 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]

迭代实现有效,但对于 100000-D 向量,您选择这样做会付出巨大的成本~ 70 [ms]

>>> test_SERIAL_python( nLOOPs = 100000 ) 70318L 69211L 77825L 70943L 74834L 73079L
使用更合适/合适的工具仅需约 0.2 [ms]


即++快 350 倍

>>> aClk.start();arrayNP[:] = arrayNP[:]**2;aClk.stop() 189L 171L 173L 187L 183L 188L 193L
还有另一个小故障,又名就地操作方式:

>>> aClk.start();arrayNP[:] *=arrayNP[:];aClk.stop() 138L 139L 136L 137L 136L 136L 137L
仅使用适当的工具即可提高〜+514x速度

性能的艺术不在于遵循听起来像营销的主张

关于
并行化-(不惜一切代价,而在于使用基于专业知识的方法,以最少的成本实现最大的加速。

对于“小”问题,分发“薄”工作包的典型成本确实很难通过任何可能实现的加速来覆盖,因此“问题大小”实际上限制了人们对方法的选择,这可能会达到积极的效果增益

(加速0.9甚至<< 1.0 are so often reported here, on Stack Overflow, that you need not feel lost or alone in this sort of surprise ).


尾声

处理器数量很重要。

核心数量很重要。
但缓存大小 + NUMA 不规则性的影响远不止于此。
智能、矢量化、HPC 固化、无 GIL 库很重要
(
numpy

 等人 - 非常感谢 Travis OLIPHANT 等人...向他的团队致敬...)


作为一个开销严格阿姆达尔定律(重新)制定解释为什么甚至许多-N

-CPU
并行代码执行可能(而且确实经常如此)受到速度的影响-上升<< 1.0

阿姆达尔定律加速的开销严格制定

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

用于 2D 可视化效果的交互式动画工具这些性能限制被引用此处


4
投票
threading

文档


如果您知道您正在调用的函数是基于已编译的函数 释放 Python 全局解释器锁 (GIL) 的扩展 在其大部分计算过程中...

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

threading

将大部分时间花在已编译的 Python 扩展中,

并且该扩展释放了 GIL

 时,
myfun()
才会有用。

Parallel

代码慢得令人尴尬,因为你需要做大量的工作来创建多个线程 - 然后你一次只能执行一个线程。


如果您使用

multiprocessing

后端,那么您必须将输入数据复制到四个或八个进程(每个核心一个)中的每一个中,在每个进程中进行处理,然后将输出数据复制回来。复制会很慢,但如果处理比仅仅计算平方更复杂一点,那么可能是值得的。测量一下看看。

    

© www.soinside.com 2019 - 2024. All rights reserved.