Parralelized代码在Python中比在Matlab中运行得慢得多

时间:2013-04-22 22:03:06

标签: python matlab python-2.7 numpy multiprocessing

我有一段代码可以执行以下操作:

for each file (already read in the RAM):
    call a function and obtain a result
add the results up and disply

可以并行分析每个文件。分析每个文件的功能如下:

# Complexity = 1000*19*19 units of work
def fun(args):
    (a, b, p) = args
    for itr in range(1000):
        for i in range(19):
            for j in range(19):
                # The following random number generated depends on
                # latest values in (i-1, j), (i+1, j), (i, j-1) & (i, j+1)
                # cells of latest a and b arrays
                u = np.random.rand();
                if (u < p):
                    a[i, j] += -1
                else:
                    b[i, j] += 1
    return a+b

我正在使用multiprocessing包来实现并行性:

import numpy as np
import time
from multiprocessing import Pool, cpu_count

if __name__ == '__main__':
    t = time.time()
    pool = Pool(processes=cpu_count())
    args = [None]*100
    for i in range(100):
        a = np.random.randint(2, size=(19, 19))
        b = np.random.randint(2, size=(19, 19))
        p = np.random.rand()
        args[i] = (a, b, p)
    result = pool.map(fun, args)
    for i in range(2, 100):
        result[0] += result[i]
    print result[0]
    print time.time() - t

我编写了使用MATLAB的等效parfor代码,并在fun的每次迭代中调用parfor

tic
args = cell(100, 1);
r = cell(100, 1);
parfor i = 1:100
   a = randi(2, 19, 19);
   b = randi(2, 19, 19);
   p = rand();
   args{i}.a = a;
   args{i}.b = b;
   args{i}.p = p;
   r{i} = fun(args{i});
end

for i = 2:100
    r{1} = r{1} + r{i};
end
disp(r{1});
toc

fun的实施如下:

function [ ret ] = fun( args )
a = args.a;
b = args.b;
p = args.p;

for itr = 1:1000
    for i = 1:19
        for j = 1:19
            u = rand();
            if (u < p)
                a(i, j) = a(i, j) + -1;
            else
                b(i, j) = b(i, j) + 1;
            end
        end
    end
end
ret = a + b;
end

我发现MATLAB速度非常快,双核处理器大约需要1.5秒,而Python程序大约需要33-34秒。为什么会这样?

编辑:很多答案都表明我应该对随机数生成进行矢量化。实际上它的工作方式是,生成的随机数取决于最新的a和b 2D阵列。我只是简单地调用rand()来保持程序简单易读。在我的程序的实际情况中,总是通过查看(i,j)单元的某些水平和垂直相邻单元来生成随机数。因此无法对其进行矢量化。

4 个答案:

答案 0 :(得分:2)

您是否在非并行上下文中对fun的两种实现进行了基准测试?一个人可能会快得多。特别是,Python fun中的那些嵌套循环看起来可能会在Matlab中转换为更快的矢量化解决方案,或者可能会被Matlab的JIT优化。

将两种实施方法都放在剖析器中,以了解他们在哪里花费时间。将两个实现转换为非并行并首先对它们进行分析,以确保它们在引入并行化内容的复杂性之前在性能上具有相同性。

最后一次检查 - 您是否正在使用本地工作池设置Matlab的并行计算工具箱,对,而不是挂钩到远程计算机或获取其他一些资源? Matlab方面有多少工人?

答案 1 :(得分:1)

我对你的Python代码进行了一些测试,虽然没有multiprocessing部分,并通过进行以下更改实现了大约25倍的加速:

  • 使用Python列表而不是NumPy数组,因为后者在需要进行大量索引时非常慢。我的计时时间包括ndarray.tolist()所需的时间,所以这实际上可能是一个可行的选择,只要我认为阵列不是很大。
  • 将它放在PyPy而不是常规的Python解释器中,因为PyPy有一个JIT编译器,使得与MATLAB的比较更加公平。常规的CPython没有这样的功能。
  • 对函数进行局部“随机”调用,即执行rand = np.random.rand和更晚u = rand(),因为在Python中,本地命名空间中的查找更快,这​​在紧密循环中很重要,例如
  • 使用Python的random.random而不是np.random.rand(也绑定到函数的本地名称)。
  • 为基于生成器的range
  • 兑换xrange

(此列表从最重要的加速到非常小的增益排序)

当然还有并行计算方面。使用multiprocessing所有在进程之间传递的Python对象都是“pickle”的,这意味着它们必须在进程之间复制时进行序列化和反序列化。 MATLAB还可以在进程(或线程?)之间复制数据,但可能会以较少浪费的方式进行复制。接下来,设置multiprocessing.Pool也需要一段时间,这对您的MATLAB基准测试可能不公平,但我不确定。

基于你的时间和我的时间,我会说Python和MATLAB对于这个特定的任务可能同样快。但是,不幸的是,你必须跳过一些箍来获得Python的速度。如果你有可能的话,使用Numba的@autojit能力可能会很有趣。

答案 2 :(得分:0)

鉴于可用的信息,你可能会从使用Cython中受益,让你也可以使用一些parallelism。根据您需要的随机数,您可以使用GSL生成它们。

原始问题

无需使用multiprocessing,因为fun可以很容易地进行矢量化,从而产生大幅加速(超过50倍)。

我不确定为什么matlab不会像numpy那样难受,但它的JIT可能会保存它。 Python不喜欢在内部循环中进行两次.查找,也不喜欢在那里调用昂贵的函数。

def fun_fast(args):
     a, b, p = args
     for i in xrange(19):
         for j in xrange(19):
             u = np.random.rand(1000)
             msk = u < p
             msk_sum = msk.sum()
             a[i, j] -= msk_sum
             b[i, j] += msk.size - msk_sum
     return a + b

答案 3 :(得分:0)

尝试此版本的乐趣,看看它是否能为您提供加速。

def fun(args):
    a, b, p = args
    n = 1000
    u = np.random.random((n, 19, 19))
    msk = u < p
    msk_sum = msk.sum(0)
    a -= msk_sum
    b += (n - msk_sum)

    return a + b

这是使用numpy实现此类函数的更有效方法。

这些嵌套循环在解释语言(如matlab和python)中可能会有很高的开销,但我怀疑JIT在matlab中至少部分补偿,因此矢量化和循环实现之间的性能差异会更小。 Cpython目前没有对这些类型的循环进行任何优化(我知道)但是至少有一个python实现pypy确实有一个JIT。不幸的是,pypy目前只有有限的numpy支持。

更新

看起来你有一个迭代算法,至少在我的经验中,那些是最难用numpy / cpython进行优化的。考虑使用cython,这个tutorial也可能有用,可以编写嵌套循环。其他人可能有其他建议,但这是我能想到的最好的建议。