Cython的prange不能提高性能

8

我正在使用Cython的prange来提高某些度量计算的性能。以下是我的代码:

def shausdorff(float64_t[:,::1] XA not None, float64_t[:,:,::1] XB not None):
    cdef:
        Py_ssize_t i
        Py_ssize_t n  = XB.shape[2]
        float64_t[::1] hdist = np.zeros(n)

    #arrangement to fix contiguity
    XB = np.asanyarray([np.ascontiguousarray(XB[:,:,i]) for i in range(n)])

    for i in range(n):
        hdist[i] = _hausdorff(XA, XB[i])
    return hdist

def phausdorff(float64_t[:,::1] XA not None, float64_t[:,:,::1] XB not None):
    cdef:
        Py_ssize_t i
        Py_ssize_t n  = XB.shape[2]
        float64_t[::1] hdist = np.zeros(n)

    #arrangement to fix contiguity (EDITED)
    cdef float64_t[:,:,::1] XC = np.asanyarray([np.ascontiguousarray(XB[:,:,i]) for i in range(n)])

    with nogil, parallel(num_threads=4):
        for i in prange(n, schedule='static', chunksize=1):
            hdist[i] = _hausdorff(XA, XC[i])
    return hdist

基本上,每次迭代中都会在XA和每个XB[i]之间计算hausdorff度量。以下是_hausdorff函数的签名:

cdef inline float64_t _hausdorff(float64_t[:,::1] XA, float64_t[:,::1] XB) nogil:
    ...

我的问题是顺序shausdorff和并行phausdorff的时间相同。此外,似乎phausdorff根本没有创建线程。
那么我的问题是,我的代码有什么问题,我如何修复它以使线程工作。
这是我的setup.py:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext

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

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

编辑1: 这里是由 cython -a 生成的html链接:custom_metric.html

编辑2: 这是一个调用相应函数的示例(您需要首先编译Cython文件

import custom_metric as cm
import numpy as np

XA = np.random.random((9000, 210))
XB = np.random.random((1000, 210, 9))

#timing 'parallel' version
%timeit cm.phausdorff(XA, XB)

#timing sequential version
%timeit cm.shausdorff(XA, XB)

你尝试在prange循环体内打印等效于omp_get_thread_num()的内容了吗?请参考http://cython.readthedocs.io/en/latest/src/userguide/parallelism.html。 - Harald
2
可能XB是一个Python对象?使用注释运行cython -a custom_metric.pyx - cgohlke
1
@mavillan 你在使用OSX吗?因为我在互联网上看到的信息表明,他们提供的clang版本不支持OpenMP。请参见https://dev59.com/eJHea4cB1Zd3GeqPjQP4,但我自己没有使用OSX,无法确认... - DavidW
2
@mavillan,您能提供如何调用shausdorffphausdorff的小例子吗? - Harald
1
你是否考虑过使用更大的“chunk_size”?根据算法,如果使用“1”的“chunk_size”,可能会导致非常差的数据局部性。 - smateo
显示剩余5条评论
1个回答

4

我认为并行化正在起作用,但是并行化的额外开销正在消耗本应该节省下来的时间。如果我尝试使用不同大小的数组,则可以看到并行版本的加速。

XA = np.random.random((900, 2100))
XB = np.random.random((100, 2100, 90))

这里并行版本的运行时间只需要串行版本的2/3左右,虽然不是你预期的1/4,但至少表明有一些好处。


我可以提供一个改进方案,即替换修复连续性的代码:

XB = np.asanyarray([np.ascontiguousarray(XB[:,:,i]) for i in range(n)]) 

使用

XB = np.ascontiguousarray(np.transpose(XB,[2,0,1]))

这将显著加快并行和非并行函数的执行速度(在您最初提供的数组中,速度增加了两倍)。它确实使prange的开销变得更加明显——在您的示例中,串行版本实际上比并行版本更快。


由于这不是一个解决方案,所以我将其发布为社区wiki。以下是有关编程的内容的翻译: - DavidW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接