12

考虑以下最小示例:

#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True
cimport cython
from libc.stdlib cimport malloc

def main(size_t ni, size_t nt, size_t nx):
    cdef:
        size_t i, j, t, x, y
        double[:, :, ::1] a = <double[:ni, :ni, :nx]>malloc(ni * ni * nx * sizeof(double))
        double[:, :, ::1] b = <double[:nt, :ni, :nx]>malloc(nt * ni * nx * sizeof(double))
        size_t[:, :, ::1] best = <size_t[:nt, :ni, :nx]>malloc(nt * ni * nx * sizeof(size_t))
        size_t mxi
        double s, mxs
    for t in range(nt):
        for j in range(ni):
            for y in range(nx): # this loops does nothing but is needed for the effect below.
                mxs = -1e300
                for i in range(ni):
                    for x in range(nx):
                        with cython.boundscheck(False): # Faster!?!?
                            s = b[t, i, x] + a[i, j, x]
                        if s >= mxs:
                            mxs = s
                            mxi = i
                best[t + 1, j, y] = mxi
    return best[0, 0, 0]

基本上沿某些特定轴对两个二维数组求和,并沿另一个轴找到最大化索引。

当使用 gcc -O3 编译并使用参数 (1, 2000, 2000) 调用时,添加 boundscheck=True 导致执行速度比 boundscheck=False 快两倍。

任何暗示为什么会这样?(好吧,我可能猜到这又与 GCC 自动矢量化有关……)

提前致谢。

(来自 cython 用户的交叉发布)

4

1 回答 1

1

Boundscheck 是一种安全检查,您正在访问向量边界内的索引。如果您不费心检查索引是否会超出范围,那么它会更快。执行检查需要时间。

也就是说,如果 boundcheck 为真,它将在读取或写入内存之前检查索引是否在向量范围内。如果不是,它将引发错误。如果 boundcheck 为假,即使索引超出范围,它也会读取或写入指针,通过读取和写入内存来给出错误数据,通过写入破坏数据。

从文档:

数组查找仍然因两个因素而减慢:

1) 执行边界检查。

2) 检查并正确处理负索引。

未绑定检查的后果是:

现在不执行边界检查(并且,作为副作用,如果您“确实”碰巧访问越界,您在最好的情况下会使您的程序崩溃,在最坏的情况下损坏数据)。

这一点特别重要的是你可以有 None 向量。这是文档中的警告:

警告

速度伴随着一些成本。将类型化对象(如我们示例代码中的 f、g 和 h)设置为 None 尤其危险。将此类对象设置为 None 是完全合法的,但您可以对它们做的就是检查它们是否为 None。所有其他用途(属性查找或索引)都可能存在段错误或损坏数据(而不是像在 Python 中那样引发异常)。

实际规则有点复杂,但主要信息很清楚:不要在不知道它们未设置为 None 的情况下使用类型化对象。

http://docs.cython.org/src/userguide/numpy_tutorial.html

于 2015-06-09T23:25:00.777 回答