我需要的性能可以从高性能数字代码使用用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?
你在你的代码的两个问题(使用选项-a
以使其可见):
- numpy的阵列的索引是不高效的
- 你已经忘记
int
在cdef 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-模块。