基于numpy的计算的低效多处理

时间:2015-03-31 01:02:41

标签: python numpy multiprocessing

我尝试在Python的numpy模块的帮助下并行化使用multiprocessing的一些计算。考虑这个简化的例子:

import time
import numpy

from multiprocessing import Pool

def test_func(i):

    a = numpy.random.normal(size=1000000)
    b = numpy.random.normal(size=1000000)

    for i in range(2000):
        a = a + b
        b = a - b
        a = a - b

    return 1

t1 = time.time()
test_func(0)
single_time = time.time() - t1
print("Single time:", single_time)

n_par = 4
pool = Pool()

t1 = time.time()
results_async = [
    pool.apply_async(test_func, [i])
    for i in range(n_par)]
results = [r.get() for r in results_async]
multicore_time = time.time() - t1

print("Multicore time:", multicore_time)
print("Efficiency:", single_time / multicore_time)

当我执行它时,multicore_time大致等于single_time * n_par,而我希望它接近single_time。实际上,如果我用numpy替换time.sleep(10)计算,这就是我得到的 - 完美的效率。但由于某种原因,它不适用于numpy。这可以解决,还是numpy的内部限制?

其他一些可能有用的信息:

  • 我使用OSX 10.9.5,Python 3.4.2和CPU是Core i7(由系统信息报告)4核(尽管上述程序只占用CPU时间的50%)总的来说,系统信息可能没有考虑超线程。)

  • 当我运行此操作时,我看到n_par中的top进程工作在100%CPU

  • 如果我用循环和每索引操作替换numpy数组操作,效率会显着提高(n_par = 4约为75%)。

3 个答案:

答案 0 :(得分:8)

看起来你正在使用的测试功能是内存限制。这意味着您所看到的运行时间受到计算机将阵列从内存拉入缓存的速度的限制。例如,行a = a + b实际上使用了3个数组,ab以及将替换a的新数组。这三个数组各约为8MB(1e6浮点数*每个浮点数8个字节)。我相信不同的i7有3MB - 8MB的共享L3缓存,所以你不能同时在缓存中容纳所有3个阵列。你的cpu添加浮点数比可以加载到缓存中的数组更快,所以大部分时间都花在等待数组从内存中读取。由于缓存是在核心之间共享的,因此您无法通过将工作分散到多个核心来看到任何加速。

内存绑定操作一般都是numpy的问题,我知道处理它们的唯一方法是使用像cython或numba这样的东西。

答案 1 :(得分:4)

如果可能的话,应该提高效率的一件简单事情应该是进行就地数组操作 - 因此add(a,b,a)创建一个新数组,而{{1}将会。如果你的a = a + b数组的for循环可以重写为向量运算,那么它也应该更高效。另一种可能性是使用numpy来启用共享内存numpy数组(请参阅:https://stackoverflow.com/a/5550156/2379433)。

答案 2 :(得分:2)

我一直在为数学编写数值方法并遇到同样的问题:我没有看到任何加速的假设cpu有界问题。事实证明我的问题是达到CPU缓存内存限制。

我一直在使用英特尔PCM(英特尔®性能计数器监视器)来查看cpu缓存内存的行为(在Linux ksysguard中显示)。我还禁用了2个处理器以获得更清晰的结果(2个处于活动状态)。

以下是我使用此代码发现的内容:

def somethinglong(b):
    n=200000
    m=5000
    shared=np.arange(n)
    for i in np.arange(m):
        0.01*shared

pool = mp.Pool(2)
jobs = [() for i in range(8)]
for i in range(5):
    timei = time.time()
    pool.map(somethinglong, jobs , chunksize=1)
    #for job in jobs:
       #somethinglong(job)
print(time.time()-timei)

未达到缓存限制的示例:

  • N = 10000
  • M = 100000
  • 顺序执行:15s
  • 2个处理器池没有缓存内存限制:8s

可以看出,没有缓存未命中(所有缓存命中),因此加速几乎是完美的:15/8。 Memory cache hits 2 pool

达到缓存限制的示例:

  • n = 200000
  • M = 5000
  • 顺序执行:14s
  • 2个处理器池缓存内存限制:14s

在这种情况下,我增加了我们操作的向量的大小(并减小了循环大小,以查看合理的执行时间)。在这种情况下,我们可以看到内存已满,进程总是错过高速缓存。因此没有获得任何加速:15/15。 Memory cache misses 2 pool

观察:为变量分配操作(aux = 0.01 * shared)也使用缓存,并且可以通过内存限制问题(不增加任何矢量大小)。