15

为了在 cython 中快速除法,我可以使用编译器指令

@cython.cdivision(True)

这是可行的,因为生成的 c 代码没有零除法检查。但是由于某种原因,它实际上使我的代码变慢了。这是一个例子:

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True)
def example1(double[:] xi, double[:] a, double[:] b, int D):

    cdef int k
    cdef double[:] x = np.zeros(D)

    for k in range(D):
        x[k] = (xi[k] - a[k]) / (b[k] - a[k]) 

    return x

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def example2(double[:] xi, double[:] a, double[:] b, int D):

    cdef int k
    cdef double[:] x = np.zeros(D)

    for k in range(D):
        x[k] = (xi[k] - a[k]) / (b[k] - a[k]) 

    return x

def test_division(self):

    D = 10000
    x = np.random.rand(D)
    a = np.zeros(D)
    b = np.random.rand(D) + 1

    tic = time.time()
    example1(x, a, b, D)
    toc = time.time()

    print 'With c division: ' + str(toc - tic)

    tic = time.time()
    example2(x, a, b, D)
    toc = time.time()

    print 'Without c division: ' + str(toc - tic)

这导致输出:

With c division: 0.000194787979126
Without c division: 0.000176906585693

是否有任何理由关闭零除数检查会减慢速度(我知道没有零除数)。

4

2 回答 2

15

首先,您需要多次(>1000)次调用这些函数,并计算每个函数花费的平均时间,以准确了解它们的不同之处。调用每个函数一次将不够准确。

其次,在函数中花费的时间会受到其他东西的影响,而不仅仅是带有除法的循环。像这样调用defie Python 函数在传递和返回参数时会产生一些开销。此外,在函数中创建一个numpy数组需要时间,因此两个函数中循环的任何差异都不太明显。

最后,请参见此处(https://github.com/cython/cython/wiki/enhancements-compilerdirectives),将 c-division 指令设置为False会导致约 35% 的速度损失。考虑到其他开销,我认为这还不足以显示在您的示例中。我检查了Cython输出的C代码,example2的代码明显不同,并且包含额外的零除法检查,但是当我对其进行分析时,运行时间的差异可以忽略不计。

为了说明这一点,我运行了下面的代码,我在其中获取了您的代码并将def函数制成cdef函数,即Cython函数而不是Python函数。这大大减少了传递和返回参数的开销。我还更改了example1example2,只计算 numpy 数组中的值的总和,而不是创建一个新数组并填充它。这意味着现在每个函数中花费的几乎所有时间都在循环中,因此应该更容易看到任何差异。我也多次运行每个函数,并使 D 更大。

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.cdivision(True) 
@cython.profile(True)
cdef double example1(double[:] xi, double[:] a, double[:] b, int D):

    cdef int k
    cdef double theSum = 0.0

    for k in range(D):
        theSum += (xi[k] - a[k]) / (b[k] - a[k])

    return theSum

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
@cython.profile(True)
@cython.cdivision(False)
cdef double example2(double[:] xi, double[:] a, double[:] b, int D):

    cdef int k
    cdef double theSum = 0.0

    for k in range(D):
        theSum += (xi[k] - a[k]) / (b[k] - a[k])

    return theSum


def testExamples():
    D = 100000
    x = np.random.rand(D)
    a = np.zeros(D)
    b = np.random.rand(D) + 1

    for i in xrange(10000):
        example1(x, a, b, D)
        example2(x, a, b,D) 

我通过分析器(python -m cProfile -s 累积)运行了这段代码,相关输出如下:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
10000    1.546    0.000    1.546    0.000 test.pyx:26(example2)
10000    0.002    0.000    0.002    0.000 test.pyx:11(example1)

这表明 example2 慢得多。如果我在 example2 中打开 c-division,那么 example1 和 example2 所花费的时间是相同的,所以这显然有很大的影响。

于 2013-10-24T20:31:56.940 回答
3

我的问题是我看到装配中发生的一切。试图使用一种语言来告诉另一种语言完全按照我的意愿去做以提高性能似乎比它需要的更令人沮丧和困难。例如,Seymour Cray 总是以这种方式重铸师。 C=A/B变成:

R = reciprocalApprox(B);
R = reciprocalImprove(R);   //M-Add performed 3x to get an accurate 1/B
C = A * R;

有一次 Cray 被问到为什么要使用这种 Newton-Raphson 方法,他解释说,与硬件划分相比,他可以通过管道获得更多的操作。AMD 的 3DNow 使用了类似的方法,但使用了 32 位浮点数。对于使用 gcc 的 SSE,请尝试使用-mrecipflag 以及-funsafe-math-optimizations, -finite-math-only, 和-fno-trapping-math. 臭名昭著的-ffast-math选项也实现了这一点。您在最后一个位置失去 2 个 ulps 或单位。

http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html

您甚至可能想尝试 libdivide.h(位于 libdivide.com)。它非常依赖内存并使用一系列“便宜”的移位和乘法,最终比整数除法快十倍。它还为编译器生成 C 或 C++ 代码。

于 2013-11-08T07:53:33.383 回答