在 Cython 脚本中使用 memset 而不是 np.zeros 来提高速度
Using memset instead of np.zeros in a Cython script for speed gains
我开始研究 Fortran 库 (BLAS/LAPACK) 的 SciPy 接口,如您所见: 并提出了一个解决方案,但不得不求助于使用 numpy.zeros
这实际上扼杀了直接调用 Fortran 代码带来的任何速度提升。问题是 Fortran 代码需要一个 0 值输出矩阵(它在内存中就地操作矩阵)以匹配 Numpy 版本 (np.outer
)。
所以我有点困惑,因为 Python 中的 1000x1000 零矩阵只需要 8us(使用 %timeit,或 0.008ms)那么为什么添加 Cython 代码会终止运行时,注意我也在创建它在内存视图上? (基本上它在 1000 x 1000 矩阵乘法上从 3ms 到 8ms 左右)。然后在 SO 上四处搜索后,我在其他地方找到了一个解决方案,使用 memset
作为最快的数组更改机制。因此,我将引用的 post 中的整个代码重写为较新的 memoryview
格式,并得到了类似这样的内容(Cython cyblas.pyx
文件):
import cython
import numpy as np
cimport numpy as np
from libc.string cimport memset #faster than np.zeros
REAL = np.float64
ctypedef np.float64_t REAL_t
ctypedef np.uint64_t INT_t
cdef int ONE = 1
cdef REAL_t ONEF = <REAL_t>1.0
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cpdef outer_prod(double[::1] _x, double[::1] _y, double[:, ::1] _output):
cdef int M = _y.shape[0]
cdef int N = _x.shape[0]
memset(&_output[0,0], 0, M*N)
with nogil:
dger(&M, &N, &ONEF, &_y[0], &ONE, &_x[0], &ONE, &_output[0,0], &M)
测试脚本
import numpy as np;
from cyblas import outer_prod;
a=np.random.randint(0,100, 1000);
b=np.random.randint(0,100, 1000);
a=a.astype(np.float64)
b=b.astype(np.float64)
cy_outer=np.zeros((a.shape[0],b.shape[0]));
np_outer=np.zeros((a.shape[0],b.shape[0]));
%timeit outer_prod(a,b, cy_outer)
%timeit np.outer(a,b, np_outer)
因此,这解决了我重置输出矩阵值的问题,但仅限于第 125 行,超出此问题仍然存在(已解决,请参见下文)。我认为将 memset
长度参数设置为 M*N 会清除内存中的 1000*1000 但显然不会。
有没有人知道如何使用 memset
将整个输出矩阵重置为 0?非常感谢。
[更新 - 修复是:它需要 #bytes 而不仅仅是 M*N
的数组大小,即 M*N*variable_bytes
作为长度输入。之前的结果可以看出这里的逻辑:第 125 行是它停止的地方 *8 字节 = 1000 行,因此回答了这个问题。指标仍然不是很好:100 个循环,3 个循环中的最佳:每个循环 5.41 毫秒(cython) 100 个循环,3 个循环中的最佳:每个循环 3.95 毫秒(numpy)但仍然解决了。
对以上代码的改动是增加:cdef variable_bytes = np.dtype(REAL).itemsize #added to get bytes for memset, after REAL is defined, in this case 8 bytes
然后当你调用 memset 时: memset(&_output[0,0], 0, M*N*variable_bytes) # gives the same output as np.zeros function
现在我能看到的唯一可以加快速度的地方是在大型矩阵上使用 prange
OpenMP 语句,但还有待观察。
我开始研究 Fortran 库 (BLAS/LAPACK) 的 SciPy 接口,如您所见:numpy.zeros
这实际上扼杀了直接调用 Fortran 代码带来的任何速度提升。问题是 Fortran 代码需要一个 0 值输出矩阵(它在内存中就地操作矩阵)以匹配 Numpy 版本 (np.outer
)。
所以我有点困惑,因为 Python 中的 1000x1000 零矩阵只需要 8us(使用 %timeit,或 0.008ms)那么为什么添加 Cython 代码会终止运行时,注意我也在创建它在内存视图上? (基本上它在 1000 x 1000 矩阵乘法上从 3ms 到 8ms 左右)。然后在 SO 上四处搜索后,我在其他地方找到了一个解决方案,使用 memset
作为最快的数组更改机制。因此,我将引用的 post 中的整个代码重写为较新的 memoryview
格式,并得到了类似这样的内容(Cython cyblas.pyx
文件):
import cython
import numpy as np
cimport numpy as np
from libc.string cimport memset #faster than np.zeros
REAL = np.float64
ctypedef np.float64_t REAL_t
ctypedef np.uint64_t INT_t
cdef int ONE = 1
cdef REAL_t ONEF = <REAL_t>1.0
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cpdef outer_prod(double[::1] _x, double[::1] _y, double[:, ::1] _output):
cdef int M = _y.shape[0]
cdef int N = _x.shape[0]
memset(&_output[0,0], 0, M*N)
with nogil:
dger(&M, &N, &ONEF, &_y[0], &ONE, &_x[0], &ONE, &_output[0,0], &M)
测试脚本
import numpy as np;
from cyblas import outer_prod;
a=np.random.randint(0,100, 1000);
b=np.random.randint(0,100, 1000);
a=a.astype(np.float64)
b=b.astype(np.float64)
cy_outer=np.zeros((a.shape[0],b.shape[0]));
np_outer=np.zeros((a.shape[0],b.shape[0]));
%timeit outer_prod(a,b, cy_outer)
%timeit np.outer(a,b, np_outer)
因此,这解决了我重置输出矩阵值的问题,但仅限于第 125 行,超出此问题仍然存在(已解决,请参见下文)。我认为将 memset
长度参数设置为 M*N 会清除内存中的 1000*1000 但显然不会。
有没有人知道如何使用 memset
将整个输出矩阵重置为 0?非常感谢。
[更新 - 修复是:它需要 #bytes 而不仅仅是 M*N
的数组大小,即 M*N*variable_bytes
作为长度输入。之前的结果可以看出这里的逻辑:第 125 行是它停止的地方 *8 字节 = 1000 行,因此回答了这个问题。指标仍然不是很好:100 个循环,3 个循环中的最佳:每个循环 5.41 毫秒(cython) 100 个循环,3 个循环中的最佳:每个循环 3.95 毫秒(numpy)但仍然解决了。
对以上代码的改动是增加:cdef variable_bytes = np.dtype(REAL).itemsize #added to get bytes for memset, after REAL is defined, in this case 8 bytes
然后当你调用 memset 时: memset(&_output[0,0], 0, M*N*variable_bytes) # gives the same output as np.zeros function
现在我能看到的唯一可以加快速度的地方是在大型矩阵上使用 prange
OpenMP 语句,但还有待观察。