4

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

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和调用的等效代码:funparfor

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

4 回答 4

2

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

将这两种实现都放在分析器中,以查看它们将时间花在哪里。将这两种实现都转换为非并行并首先对它们进行分析,以确保它们在性能上是等效的,然后再引入并行化的复杂性。

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

于 2013-04-22T22:37:55.190 回答
1

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

  • 使用 Python 列表而不是 NumPy 数组,因为后者在您需要进行大量索引时真的很慢。我的时间安排包括这样做所需的时间,ndarray.tolist()这实际上可能是一个可行的选择,只要我认为阵列不是很大。
  • 在 PyPy 而不是常规的 Python 解释器中运行它,因为 PyPy 有一个 JIT 编译器,以使与 MATLAB 的比较更公平一点。常规 CPython 没有这样的功能。
  • 对函数进行本地“随机”调用,即 dorand = np.random.rand和 later u = rand(),因为在 Python 中,本地命名空间中的查找速度更快,这在诸如此类的紧密循环中可能很重要。
  • 使用 Pythonrandom.random代替np.random.rand(也绑定到函数的本地名称)。
  • 换成range基于生成器的xrange.

(这个列表从最显着的加速到只有非常小的增益排序)

当然还有并行计算方面。在multiprocessing进程之间传递的所有 Python 对象都是“腌制的”,这意味着它们必须在进程之间复制之前进行序列化和反序列化。MATLAB 还可以在进程(或线程?)之间复制数据,但这样做的方式可能不太浪费。除此之外,设置 amultiprocessing.Pool也需要很短的时间,这可能对您的 MATLAB 基准测试不公平,但我不确定这一点。

根据您和我的时间安排,我会说 Python 和 MATLAB 对于这项特定任务可能同样快。但是,不幸的是,您必须跳过一些障碍才能使用 Python 获得这种速度。也许@autojit在这方面使用 Numba 的功能可能会很有趣,如果你有的话。

于 2014-02-06T23:35:49.517 回答
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,本教程也可能有用,用于编写嵌套循环。其他人可能有其他建议,但这是我能想到的最好的。

于 2013-04-22T23:39:11.910 回答
0

鉴于可用信息,您可能会从使用 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
于 2013-04-22T23:07:12.067 回答