我有一段代码,做以下事情:
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秒。为什么会这样呢?
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