并行化的代码在Python中比在Matlab中运行得慢得多



我有一段代码,做以下事情:

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的每次迭代中使用parfor并调用fun:

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二维数组。我只是放了一个简单的rand()调用,以保持程序简单易读。实际上,在我的程序中,随机数总是通过查看(i, j)单元格的某些水平和垂直相邻的单元格来生成的。所以矢量化是不可能的。

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

把这两个实现放在分析器中,看看它们在哪里花费了时间。将两个实现转换为非并行,并首先对它们进行配置,以确保在引入并行化的复杂性之前它们在性能上是等效的。

最后一次检查-您正在使用本地工作池设置Matlab的并行计算工具箱,而不是连接到远程机器或拾取其他资源?有多少工作人员在Matlab这边?

我对你的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功能会很有趣,如果您有的话。

试试这个版本的fun,看看它是否能给你加速。

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(本教程可能也很有用)来编写嵌套循环。其他人可能有其他建议,但这是我能想到的最好的了。

给定可用的信息,您可能会受益于使用Cython,它还允许您使用一些并行性。根据你需要的随机数,你可以使用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

最新更新