在深入使用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在工作,但是执行时间与用串行函数获得的执行时间几乎相同。我试图将prange移到内部循环,但结果却是最糟糕的。我也尝试了一些不同的schedule选项,但没有成功。
我显然错过了什么,但是什么?prange是否无法使用试图访问n+X/n元素的代码对循环进行块处理?
编辑:
我的设置:
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.0setup.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
)发布于 2018-04-18 14:01:18
这个答案将是很多猜测,但正如我们将看到的:很多都取决于硬件,所以如果没有相同的硬件,很难解释。
第一个问题是:什么是瓶颈?通过查看我假设的代码,这是一个内存绑定的任务。
为了使它更加清晰,让我们只在循环中执行以下操作:
b[ix, iz] = (a[ix+1, iz])所以没有计算,只有内存访问。
我使用IntelXeonE5-2620@2.1GHz和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正如我们所看到的,一些缓存正在进行。我们有两个数组,每个8Mb -这意味着16 8Mb的数据必须“触摸”。我的机器上的每个处理器都有15 my缓存--因此对于单个线程,数据在被重用之前就会从缓存中删除,但是如果使用这两种处理器,则会有20 my的快速缓存--因此足够大到足以保存所有数据。
这意味着我们看到的加速是由于大量的快速内存(缓存),可以利用并行版本。
让我们增加数组的大小,这样缓存甚至对于并行版本也不够大:
....
>>> 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绑定。
sin和cos是相当密集的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,这对我的机器来说是相当合理的.
显然,对于其他机器/体系结构,可以观察到不同的行为。但简单地说:
清单(在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 bhttps://stackoverflow.com/questions/49888283
复制相似问题