我有一个分析代码,使用numpy进行了一些繁重的数值运算。出于好奇,尝试使用cython对其进行几乎没有任何更改的编译,然后使用numpy部分的循环将其重写。
令我惊讶的是,基于循环的代码要快得多(8倍)。我无法发布完整的代码,但是我将一个非常简单的不相关的计算放在一起,显示出相似的行为(尽管时间差异不是很大):
版本1(无cython)
import numpy as np def _process(array): rows = array.shape[0] cols = array.shape[1] out = np.zeros((rows, cols)) for row in range(0, rows): out[row, :] = np.sum(array - array[row, :], axis=0) return out def main(): data = np.load('data.npy') out = _process(data) np.save('vianumpy.npy', out)
版本2(使用cython构建模块)
import cython cimport cython import numpy as np cimport numpy as np DTYPE = np.float64 ctypedef np.float64_t DTYPE_t @cython.boundscheck(False) @cython.wraparound(False) @cython.nonecheck(False) cdef _process(np.ndarray[DTYPE_t, ndim=2] array): cdef unsigned int rows = array.shape[0] cdef unsigned int cols = array.shape[1] cdef unsigned int row cdef np.ndarray[DTYPE_t, ndim=2] out = np.zeros((rows, cols)) for row in range(0, rows): out[row, :] = np.sum(array - array[row, :], axis=0) return out def main(): cdef np.ndarray[DTYPE_t, ndim=2] data cdef np.ndarray[DTYPE_t, ndim=2] out data = np.load('data.npy') out = _process(data) np.save('viacynpy.npy', out)
版本3(使用cython构建模块)
import cython cimport cython import numpy as np cimport numpy as np DTYPE = np.float64 ctypedef np.float64_t DTYPE_t @cython.boundscheck(False) @cython.wraparound(False) @cython.nonecheck(False) cdef _process(np.ndarray[DTYPE_t, ndim=2] array): cdef unsigned int rows = array.shape[0] cdef unsigned int cols = array.shape[1] cdef unsigned int row cdef np.ndarray[DTYPE_t, ndim=2] out = np.zeros((rows, cols)) for row in range(0, rows): for col in range(0, cols): for row2 in range(0, rows): out[row, col] += array[row2, col] - array[row, col] return out def main(): cdef np.ndarray[DTYPE_t, ndim=2] data cdef np.ndarray[DTYPE_t, ndim=2] out data = np.load('data.npy') out = _process(data) np.save('vialoop.npy', out)
将10000x10矩阵保存在data.npy中,时间为:
$ python -m timeit -c "from version1 import main;main()" 10 loops, best of 3: 4.56 sec per loop $ python -m timeit -c "from version2 import main;main()" 10 loops, best of 3: 4.57 sec per loop $ python -m timeit -c "from version3 import main;main()" 10 loops, best of 3: 2.96 sec per loop
这是预期的还是我缺少的优化?版本1和版本2给出相同结果的事实在某种程度上是可以预期的,但是为什么版本3更快?
附言:-这不是我需要进行的计算,只是一个简单的示例即可显示相同的内容。
如其他答案中所述,版本2与版本1本质上相同,因为cython无法深入研究数组访问运算符以对其进行优化。有两个原因
首先,与优化的C代码相比,每次调用numpy函数都会有一定的开销。但是,如果每个操作处理大型数组,则此开销将变得不那么重要
其次,创建中间数组。如果您考虑使用更复杂的操作(例如),则更清楚out[row, :] = A[row, :] + B[row, :]*C[row, :]。在这种情况下,B*C必须在内存中创建整个数组,然后将其添加到中A。这意味着CPU缓存正在被破坏,因为数据是从内存中读取和写入到内存中,而不是直接保存在CPU中并立即使用。重要的是,如果要处理大型阵列,此问题将变得更加严重。
out[row, :] = A[row, :] + B[row, :]*C[row, :]
B*C
A
特别是由于您声明实际代码比示例复杂,并且显示出更大的加速性,因此我怀疑第二个原因很可能是您的案例中的主要因素。
顺便说一句,如果您的计算足够简单,则可以使用numexpr克服这种影响,尽管cython当然在更多情况下很有用,所以它可能是您的更好方法。