cython prange并不比单线程快

时间:2015-12-24 14:12:36

标签: python performance parallel-processing cython

我刚刚编写了一个简单的程序来测试cython prange的执行情况,以下是代码:

from cython.parallel import prange
import numpy as np

def func(int r, int c):
  cdef:
    double[:,:] a = np.arange(r*c, dtype=np.double).reshape(r,c)
    double total = 0
    int i, j

  for i in prange(r, nogil=True, schedule='static', chunksize=1):
    for j in range(c):
      total += a[i,j]

  return total

在Mac Book Pro上,使用OMP_NUM_THREADS=3(r,c) = (10000, 100000)上面的代码大约需要18秒,单线程需要大约21秒。

为什么性能提升如此之小?我正确使用此prange吗?

1 个答案:

答案 0 :(得分:3)

您是否计划了分配a需要多长时间? 10000 x 100000 float64阵列占用8GB内存。

a = np.ones((10000, 100000), np.double)
我的笔记本电脑上有16GB内存需要6秒钟。如果你没有8GB的免费空间,那么你就可以点击交换,这将需要更长的批次。由于func几乎花费了所有时间来分配a,因此并行化外部for循环只能让您在总运行时间上获得一小部分改进。

为了证明这一点,我修改了你的函数以接受a作为输入。在tmp.pyx

#cython: boundscheck=False, wraparound=False, initializedcheck=False

from cython.parallel cimport prange

def serial(double[:, :] a):
    cdef:
        double total = 0
        int i, j
    for i in range(a.shape[0]):
        for j in range(a.shape[1]):
            total += a[i, j]
    return total

def parallel(double[:, :] a):
    cdef:
        double total = 0
        int i, j
    for i in prange(a.shape[0], nogil=True, schedule='static', chunksize=1):
        for j in range(a.shape[1]):
            total += a[i, j]
    return total

例如:

In [1]: import tmp

In [2]: r, c = 10000, 100000

In [3]: a = np.random.randn(r, c)   # this takes ~6.75 sec

In [4]: %timeit tmp.serial(a)
1 loops, best of 3: 1.25 s per loop

In [5]: %timeit tmp.parallel(a)
1 loops, best of 3: 450 ms per loop

在我的笔记本电脑上使用4个内核并行化这个功能可以获得大约2.8倍的加速*,但这只是分配a所用时间的一小部分。

这里的教训是始终对您的代码进行概要分析,以便在您进入优化之前了解其花费的大部分时间。

*您可以通过将更大的a块传递给每个工作进程来做得更好,例如通过增加chunksize或使用schedule='guided'