在cython中缓慢分裂

时间:2013-10-23 09:17:21

标签: python cython

为了在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

有没有理由为什么关闭零分割检查可以减慢事情(我知道没有零除数)。

2 个答案:

答案 0 :(得分:12)

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

其次,在函数中花费的时间将受到其他事物的影响,而不仅仅是具有分割的循环。像这样调用def即Python函数涉及传递和返回参数的一些开销。此外,在函数中创建 numpy 数组需要时间,因此两个函数中循环的任何差异都不太明显。

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

为了说明这一点,我运行了下面的代码,在那里我使用了代码并将def函数转换为cdef函数,即 Cython 函数而不是< strong> Python 函数。这大大减少了传递和返回参数的开销。我还更改了 example1 example2 ,只计算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 cumulative ),相关输出如下:

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花费的时间是相同的,所以这显然会产生重大影响。

答案 1 :(得分:2)

我的问题是我看到大会中发生的一切。尝试使用一种语言告诉另一种语言完全按照我想要的方式提取性能似乎比它需要的更令人沮丧和困难。例如,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,请尝试-mrecip标记,-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 ++代码。