高效的Cython用于大型矩阵创建/操作

时间:2013-10-18 09:12:37

标签: python performance matrix 64-bit cython

我一直在尝试加速创建和操作非常大的数据矩阵的代码段(大约15,000 x 15,000;类型为double)。现在,我不认为矩阵的大小是那么重要,因为即使对于一个小的10 x 10矩阵我也看不到加速(事实上,对于小矩阵,编译的cython代码比纯python慢​​,而时间对于大型矩阵,cython和python几乎相同。请耐心等待,因为我只编写了一个星期的python(从Matlab新转换),我只是一个不起眼的化学工程师。

代码的目标是以一维数组(长度L)作为输入,例如:

[ 16.66  16.85  16.93  16.98  17.08  17.03  17.09  16.76  16.67  16.72]

并产生矩阵(高度L,宽度L-1)作为输出:

[[ 16.66  16.85  16.93  16.98  17.08  17.03  17.09  16.76  16.67]
 [ 16.85  16.93  16.98  17.08  17.03  17.09  16.76  16.67  16.72]
 [ 16.93  16.98  17.08  17.03  17.09  16.76  16.67  16.72   0.  ]
 [ 16.98  17.08  17.03  17.09  16.76  16.67  16.72   0.     0.  ]
 [ 17.08  17.03  17.09  16.76  16.67  16.72   0.     0.     0.  ]
 [ 17.03  17.09  16.76  16.67  16.72   0.     0.     0.     0.  ]
 [ 17.09  16.76  16.67  16.72   0.     0.     0.     0.     0.  ]
 [ 16.76  16.67  16.72   0.     0.     0.     0.     0.     0.  ]
 [ 16.67  16.72   0.     0.     0.     0.     0.     0.     0.  ]
 [ 16.72   0.     0.     0.     0.     0.     0.     0.     0.  ]]

我希望从上面的示例和下面的代码中可以清楚地看到我想要实现的目标。该算法需要扩展到非常大的矩阵,它目前没有错误,它只是很慢!

这是我的cython代码:

from scipy.sparse import spdiags
import numpy as np
cimport numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def sfmat(np.ndarray[double, ndim=1] data):
    cdef int h = data.shape[0]   
    cdef np.ndarray[double, ndim=2] m = np.zeros([h, h-1])
    m = np.flipud(spdiags(np.tril(np.tile(data,[h-1,1]).T,0),range(1-h,1), h, h-1).todense())
    return m

我还尝试了更详细的代码,这些代码可能更清楚:

from scipy.sparse import spdiags
import numpy as np
cimport numpy as np
cimport cython

DTYPE = np.float
ctypedef np.float_t DTYPE_t

@cython.boundscheck(False)
@cython.wraparound(False)
def sfmat(np.ndarray[DTYPE_t, ndim=1] data):
    assert data.dtype == DTYPE
    cdef int h = data.shape[0]   
    cdef np.ndarray[DTYPE_t, ndim=2] m = np.zeros([h, h-1], dtype=DTYPE)
    cdef np.ndarray[DTYPE_t, ndim=2] s1 = np.zeros([h, h-1], dtype=DTYPE)
    cdef np.ndarray[DTYPE_t, ndim=2] s2 = np.zeros([h, h-1], dtype=DTYPE)
    cdef np.ndarray[DTYPE_t, ndim=2] s3 = np.zeros([h, h-1], dtype=DTYPE)

    s1 = np.tile(data,[h-1,1]).T
    s2 = np.tril(s1,0)
    s3 = spdiags(s2,range(1-h,1), h, h-1).todense()
    m = np.flipud(s3)
    return m

非常感谢任何有关cython实现的帮助。如果还有其他方法来加速这个算法,那也会有所帮助。 谢谢你的帮助!

因为我是新手,所以这里有更多细节,可能会或可能不会阻止我加快速度。 我正在运行64位Windows 7 Pro,并使用Windows SDK C / C ++编译器成功编译了cython代码。 (我成功地遵循了github here上的指示)。简单的“hello world”cython示例编译正常并在64位模式下运行良好,上面的代码也编译并运行,没有错误。对于整个15,000 x 15,000矩阵的操作,需要64位架构,或者至少我相信,因为在编译32位后运行代码会导致内存错误。对于这个问题,请假设将矩阵分解成较小的块是不可能的。 如果有任何其他信息需要回答这个问题,请告诉我。

干杯,科学家

更新

我认为避免循环是最好的方法,但spdiags是主要的瓶颈。因此,新算法效果更好(在我的计算机上提高了4倍):

import numpy as np
cimport numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def sfmat(np.ndarray[double, ndim=1] data):
     cdef int i
     cdef np.ndarray[double, ndim=2] m = np.zeros([data.shape[0], data.shape[0]-1])
     for i in range(data.shape[0]-1):
         m[:,i] = np.roll(data,-i);
     return m

但Cython并没有提供比纯Python更好的改进。请帮忙。正如评论家指出的那样,除了更优化的算法之外,可能没有办法改善这一点,但我很有希望。谢谢!还有,有更快的算法,cython或python?

2 个答案:

答案 0 :(得分:0)

这可能是一个古老的问题,但不应该回答任何问题:)。通过使用简单的for循环(在Cython中实际上很快),我能够将你的Cython代码加速大约8倍,对于数组大小7000,请注意使用np.roll的实现不会产生的方式你想要的数组(!),但我使用该函数来比较时间。

使用类型化的内存浏览量编辑的代码和np.empty代替np.zeros

def sfmat(double[:] data):
     cdef int n = data.shape[0]
     cdef np.ndarray[double, ndim=2] out = np.empty((n, n-1))
     cdef double [:, :] out_v = out  # "typed memoryview"

     cdef int i, j
     for i in range(n-1):
        out_v[0, i] = data[i]

     for i in range(1, n):
        for j in range(n-i):
            out_v[i, j] = data[i+j]
        for j in range(n-i, n-1):
            out_v[i, j] = 0.
     return out

不幸的是,Cython的工作量比在常规Python会话中运行以下代码快了约1.2倍:

def sfmat(data):
    n = len(data)
    out = np.empty((n, n-1))
    out[0, :] = data[:n-1]
    for i in xrange(1, n):
        out[i, :n-i] = data[i:]
        out[i, n-i:] = 0
    return out

然而,正如评论中已经讨论过的那样,以这种方式炸掉原来相当小的矩阵可能不是解决实际整体问题的最有效方法。如果您最初想要做的就是避免使用for循环,那么在Cython中根本就没有必要这样做!

答案 1 :(得分:0)

我并不是说天真,但我们都知道C,C ++和Python是“行主要”语言,对吧? Matlab(和Fortran)是“专栏”。我确定您已经尝试撤消ij,但只是想提及它,以防万一没有人想过尝试过。