【问题标题】:Cython parallelism and stencilsCython 并行性和模板
【发布时间】:2018-04-17 22:16:19
【问题描述】:

在大量使用 numba 之后,我将回到 cython 来并行化一些耗时的函数。以下是一个基本示例:

import numpy as np
cimport numpy as np

from cython import boundscheck, wraparound
from cython.parallel import parallel, prange

@boundscheck(False)
@wraparound(False)
def cytest1(double[:,::1] a, double[:,::1] b, int ix1, int ix2, int iz1, int iz2):

    cdef int ix
    cdef int iz

    for ix in range(ix1, ix2):
        for iz in range(iz1, iz2):
            b[ix, iz] = 0.5*(a[ix+1, iz] - a[ix-1, iz])
    return b


@boundscheck(False)
@wraparound(False)
def cytest2(double[:,::1] a, double[:,::1] b, int ix1, int ix2, int iz1, int iz2):

    cdef int ix
    cdef int iz

    with nogil, parallel():
        for ix in prange(ix1, ix2):
            for iz in range(iz1, iz2):
                b[ix, iz] = 0.5*(a[ix+1, iz] - a[ix-1, iz])

    return b

编译这两个函数时(带有openmp标志),并调用它们如下:

nx, nz = 1024, 1024

a = np.random.rand(nx, nz)
b = np.zeros_like(a)

Nit = 1000
ti = time.time()
for i in range(Nit):
    cytest1(a, b, 5, nx-5, 0, nz)
print('cytest1 : {:.3f} s.'.format(time.time() - ti))

ti = time.time()
for i in range(Nit):
    cytest2(a, b, 5, nx-5, 0, nz)
print('cytest2 : {:.3f} s.'.format(time.time() - ti))

我获得了这些执行时间:

cytest1 : 1.757 s.
cytest2 : 1.861 s.

执行并行函数时,我可以看到我的 4 个 cpu-s 在运行,但执行时间几乎与使用串行函数获得的相同。我试图将prange 移动到内部循环,但结果最差。我还尝试了一些不同的schedule 选项,但没有成功。

我显然遗漏了一些东西,但是什么? prange 是否无法使用试图访问 n+X/n-X 个元素的代码来分块循环?

编辑:

我的设置:

model name      : Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
MemTotal        : 8052556 kB
Python          : 3.5.2
cython          : 0.28.2
Numpy           : 1.14.2 
Numba           : 0.37.0

setup.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext


ext_modules = [
    Extension("stencil",
              ["stencil.pyx"],
              libraries=["m"],
              extra_compile_args=["-O3", "-ffast-math", "-march=native", "-fopenmp"],
              extra_link_args=['-fopenmp'],
              )
]

setup(
  name="stencil",
  cmdclass={"build_ext": build_ext},
  ext_modules=ext_modules
)

【问题讨论】:

  • 这是一个内存绑定的任务,因此并行化没有帮助,因为它不会增加内存带宽。更糟糕的是:它有一些开销,这使它变慢或可能导致更多的缓存未命中。到目前为止,我的理论..您可以通过在公式中使用诸如 sin 或 cos 之类的 cpu 重的东西来测试它,看看在这种情况下并行化是否有一些好处。
  • 实际上,在我的机器上,每次调用您的代码我有 2.9 毫秒与 0.27 毫秒。所以有一个加速。
  • 在 Core i7-4771 上,我得到 1.6s 的单线程和 1.0s 的多线程方法。 (Numba 0.38RC1)。因此,如果您没有最差的处理器或 RAM,则应该有优化潜力。
  • @ead 你在哪台机器上? (工作系统/处理器/编译器,..)我得到了与 Numba 和 Cython 完全相同的结果(1.4-1.6 ms/1 ms)。我在 Windows,Python 3.6,MSVCv.1900 上。
  • @max9111 我有 Windows,Intel Xeon E5-2620 @ 2.1 Ghz,Python 3.6,Cython 0.27

标签: python cython


【解决方案1】:

这个答案将是很多猜测,但正如我们将看到的:很大程度上取决于硬件,所以如果手头没有相同的硬件,就不容易解释。

第一个问题是:瓶颈是什么?通过查看代码,我认为这是一个内存受限的任务。

为了更清晰,我们在循环中只做如下操作:

 b[ix, iz] = (a[ix+1, iz])

所以没有计算,只有内存访问。

我使用 Intel Xeon E5-2620 @ 2.1 Ghz 和 2 个处理器和 %timeit-magic 报告:

>>> %timeit cytest1(a,b,5, nx-5, 0, nz)
100 loops, best of 3: 1.99 ms per loop

>>> %timeit cytest2(a,b,5, nx-5, 0, nz)
The slowest run took 234.48 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 324 µs per loop

正如我们所见,一些缓存正在进行。我们有 2 个数组,每个 8Mb - 这意味着必须“触摸”16Mb 的数据。我机器上的每个处理器都有 15Mb 缓存 - 因此对于单个线程,数据在可以重用之前从缓存中逐出,但如果使用两个处理器,则有 20Mb 的快速缓存 - 因此足够大以保存所有数据。

这意味着我们看到的加速是由于并行化版本可以使用更多的快速内存(缓存)。

让我们增加数组的大小,这样即使并行化版本缓存也不够大:

....
>>> nx, nz = 10240, 10240 #100 times bigger
....

>>> %timeit cytest1(a,b,5, nx-5, 0, nz)
1 loop, best of 3: 238 ms per loop

>>> %timeit cytest2(a,b,5, nx-5, 0, nz)
10 loops, best of 3: 99.3 ms per loop

现在它快了大约 2 倍,这很容易解释:两个处理器的内存带宽是一个处理器的两倍,并且都被并行版本使用。

您的公式得到非常相似的结果

b[ix, iz] = 0.5*(a[ix+1, iz] - a[ix-1, iz])

这并不奇怪 - 没有足够的计算使其受 CPU 限制。

sincos 是 CPU 密集型操作,因此使用它们会使计算受 CPU 限制(完整代码参见附录):

...
b[ix, iz] = sin(a[ix+1, iz])
...
>>> %timeit cytest1(a,b,5, nx-5, 0, nz)
1 loop, best of 3: 1.6 s per loop

>>> %timeit cytest2(a,b,5, nx-5, 0, nz)
1 loop, best of 3: 217 ms per loop

这会产生 8 倍的加速,这对我的机器来说是相当合理的。

显然,对于其他机器/架构,可以观察到不同的行为。但简而言之:

  1. 我不希望您的公式有太多的加速 - 该任务受内存限制,所以问题是,您是否可以实现更高的内存访问带宽。
  2. 对于更多 CPU 密集型计算,您应该能够看到至少一些加速,但这取决于您的硬件。

列表(在 Windows 上,在 linux 上使用 -fopenmp):

%%cython --compile-args=/openmp --link-args=/openmp 
from cython.parallel import parallel, prange
from cython import boundscheck, wraparound
from libc.math cimport sin

@boundscheck(False)
@wraparound(False)
def cytest1(double[:,::1] a, double[:,::1] b, int ix1, int ix2, int iz1, int iz2):

    cdef int ix
    cdef int iz

    for ix in range(ix1, ix2):
        for iz in range(iz1, iz2):
            b[ix, iz] =sin(a[ix+1, iz])
    return b


@boundscheck(False)
@wraparound(False)
def cytest2(double[:,::1] a, double[:,::1] b, int ix1, int ix2, int iz1, int iz2):

    cdef int ix
    cdef int iz

    with nogil, parallel():
        for ix in prange(ix1, ix2):
            for iz in range(iz1, iz2):
                b[ix, iz] = sin(a[ix+1, iz])

    return b

【讨论】:

  • 感谢您的分析。我现在清楚了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
  • 2012-01-13
  • 1970-01-01
相关资源
最近更新 更多