从生成代码用Cython SIMD指令从生成代码用Cython SIMD指令(Generating

2019-05-12 06:35发布

我需要的性能可以从高性能数字代码使用用Cython得到的概述。 其中一个我感兴趣的事情是找出是否一个优化的C编译器可以通过矢量化产生用Cython代码。 所以我决定写下面的小例子:

import numpy as np
cimport numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef int f(np.ndarray[int, ndim = 1] f):
    cdef int array_length =  f.shape[0]
    cdef int sum = 0
    cdef int k
    for k in range(array_length):
        sum += f[k]
    return sum

我知道有,没有工作numpy的功能,但我想有一个简单的代码,以便了解什么是可能的用Cython。 事实证明,与生成的代码:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize("sum.pyx"))

并呼吁有:

python setup.py build_ext --inplace

生成的C代码看起来喜欢此为循环:

for (__pyx_t_2 = 0; __pyx_t_2 < __pyx_t_1; __pyx_t_2 += 1) {
  __pyx_v_sum = __pyx_v_sum + (*(int *)((char *) 
    __pyx_pybuffernd_f.rcbuffer->pybuffer.buf +
    __pyx_t_2 * __pyx_pybuffernd_f.diminfo[0].strides)));
}

与此代码的主要问题是,编译器不知道在编译时该__pyx_pybuffernd_f.diminfo[0].strides使得数组的元素是靠近在一起在存储器中。 如果没有这些信息,编译器不能有效地量化。

有没有办法做这样的事情,从用Cython?

Answer 1:

你在你的代码的两个问题(使用选项-a以使其可见):

  1. numpy的阵列的索引是不高效的
  2. 你已经忘记intcdef sum=0

考虑到这一点,我们得到:

cpdef int f(np.ndarray[np.int_t] f):  ##HERE
    assert f.dtype == np.int
    cdef int array_length =  f.shape[0]
    cdef int sum = 0                  ##HERE
    cdef int k
    for k in range(array_length):
        sum += f[k]
    return sum

对于循环下面的代码:

int __pyx_t_5;
int __pyx_t_6;
Py_ssize_t __pyx_t_7;
....
__pyx_t_5 = __pyx_v_array_length;
for (__pyx_t_6 = 0; __pyx_t_6 < __pyx_t_5; __pyx_t_6+=1) {
   __pyx_v_k = __pyx_t_6;
   __pyx_t_7 = __pyx_v_k;
   __pyx_v_sum = (__pyx_v_sum + (*__Pyx_BufPtrStrided1d(__pyx_t_5numpy_int_t *, __pyx_pybuffernd_f.rcbuffer->pybuffer.buf, __pyx_t_7, __pyx_pybuffernd_f.diminfo[0].strides)));

}

这并不坏,但不是那么容易的优化为人类写的正常码。 正如我们已经指出的那样, __pyx_pybuffernd_f.diminfo[0].strides在编译时已知,这可以防止量化。

然而,你会得到更好的效果,使用时输入内存的观点 ,即:

cpdef int mf(int[::1] f):
    cdef int array_length =  len(f)
...

这导致了更少的不透明的C代码 - 的人,至少我的编译器,可以更好地优化:

 __pyx_t_2 = __pyx_v_array_length;
  for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_k = __pyx_t_3;
    __pyx_t_4 = __pyx_v_k;
    __pyx_v_sum = (__pyx_v_sum + (*((int *) ( /* dim=0 */ ((char *) (((int *) __pyx_v_f.data) + __pyx_t_4)) ))));
  }

这里最关键的是,我们要明确的用Cython,该内存是连续的,即int[::1]相比, int[:] ,因为它被视作numpy的阵列,为此可能stride!=1必须被考虑在内。

在这种情况下,在所述用Cython生成的C语言代码的结果相同汇编作为代码我会写。 作为crisb指出,加入-march=native会导致量化,但在这种情况下,这两个功能汇编将再次略有不同。

但是,根据我的经验,编制者往往存在一些问题,以优化通过用Cython创建的循环和/或更容易错过任何一个细节,防止真正优秀的C代码生成。 所以,我的工薪马循环策略是把它们写在普通的C和使用用Cython用于包装/访问它们 - 往往是稍快,因为人们也可以使用专用的编译器标志该代码剪断,而不影响整个Cython-模块。



文章来源: Generating SIMD instructions from Cython code
标签: python cython